From 41e0fb381a82e29859f9510c2574e0cea29f1ef0 Mon Sep 17 00:00:00 2001 From: Dennis Date: Wed, 29 Jul 2026 13:29:53 +0800 Subject: [PATCH] feat(im): close shortcut capability gaps --- internal/app/schema_shortcut_contract_test.go | 8 +- internal/cli/schema_agent_metadata/chat.json | 1311 ++- internal/cli/schema_agent_metadata/index.json | 18 +- internal/cli/schema_agent_metadata_audit.json | 20 +- internal/cli/schema_catalog/catalog.json | 154 +- internal/cli/schema_catalog/tools/chat.json | 8696 ++++++++++++++++- internal/cli/schema_command_exclusions.json | 7 +- .../products/chat.json | 20 + internal/cli/schema_hints/metadata/chat.json | 65 + .../runtime-surface-completeness.json | 2 +- internal/cli/schema_hints/selection/chat.json | 102 +- internal/helpers/chat_file_shortcut.go | 43 + internal/helpers/gap_fill_test.go | 87 + internal/helpers/helpers.go | 22 + internal/shortcut/chat/chat_message.go | 331 +- .../chat/chat_message_coverage_test.go | 5 +- internal/shortcut/chat/gap_fill_test.go | 667 ++ internal/shortcut/chat/resource_download.go | 91 +- internal/shortcut/chat/unified_send.go | 249 +- internal/shortcut/chatmsg/chatmsg.go | 69 +- .../chatmsg/coverage_completion_test.go | 21 +- internal/shortcut/runner.go | 10 +- internal/shortcut/semantic_catalog.json | 18 +- internal/shortcut/smart/at_me.go | 10 +- internal/shortcut/smart/chat_messages.go | 26 +- internal/shortcut/smart/gap_fill_test.go | 188 + internal/shortcut/smart/search_msg.go | 89 +- internal/shortcut/smart/thread_replies.go | 13 +- 28 files changed, 12101 insertions(+), 241 deletions(-) create mode 100644 internal/helpers/chat_file_shortcut.go create mode 100644 internal/helpers/gap_fill_test.go create mode 100644 internal/shortcut/chat/gap_fill_test.go create mode 100644 internal/shortcut/smart/gap_fill_test.go diff --git a/internal/app/schema_shortcut_contract_test.go b/internal/app/schema_shortcut_contract_test.go index 5c29ea8f..3baf87a5 100644 --- a/internal/app/schema_shortcut_contract_test.go +++ b/internal/app/schema_shortcut_contract_test.go @@ -17,7 +17,7 @@ import ( const ( publicShortcutCount = 265 - schemaPublishedShortcutCount = 210 + schemaPublishedShortcutCount = 215 ) func TestEmbeddedSchemaCoversOrExactlyExcludesEveryPublicShortcutContract(t *testing.T) { @@ -106,7 +106,7 @@ func TestEmbeddedShortcutProgressiveQueriesReturnCompleteContracts(t *testing.T) product := executeShortcutSchemaQuery(t, "chat") productPayload, _ := product["product"].(map[string]any) - if got, want := int(product["count"].(float64)), 120; got != want { + if got, want := int(product["count"].(float64)), 125; got != want { t.Fatalf("schema chat count = %d, want %d", got, want) } summaries := schemaContractObjectSlice(productPayload["tools"]) @@ -116,8 +116,8 @@ func TestEmbeddedShortcutProgressiveQueriesReturnCompleteContracts(t *testing.T) shortcutCount++ } } - if shortcutCount != 42 { - t.Fatalf("schema chat shortcut summaries = %d, want 42", shortcutCount) + if shortcutCount != 47 { + t.Fatalf("schema chat shortcut summaries = %d, want 47", shortcutCount) } } diff --git a/internal/cli/schema_agent_metadata/chat.json b/internal/cli/schema_agent_metadata/chat.json index a067a765..9c2cb7ca 100644 --- a/internal/cli/schema_agent_metadata/chat.json +++ b/internal/cli/schema_agent_metadata/chat.json @@ -3616,6 +3616,267 @@ "当你想查看自己作为群主或管理员在管理哪些群时使用;只读分页返回,可用 --role OWNER/ADMIN 按角色过滤。" ] }, + "chat +chat-messages": { + "agent_summary": "拉取某个会话(群聊或单聊)的消息列表并投影出发言人/文本/时间", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "要跨多个会话按关键词、发送者或消息类型检索时使用 +search-msg;已有一批精确消息 ID 时使用 +messages-mget" + ], + "confirmation": "not_required", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws chat +chat-messages --group \u003copenConversationId\u003e --direction older", + "dws chat +chat-messages --open-dingtalk-id \u003copenDingTalkId\u003e --download-resources --output-dir ./downloads" + ], + "field_provenance": { + "agent_summary": { + "value": "拉取某个会话(群聊或单聊)的消息列表并投影出发言人/文本/时间", + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified group/direct routing, stable projection, pagination, and optional safe resource workflow; it distinguishes conversation history from cross-conversation search and exact-ID mget.", + "candidates": [ + { + "value": "拉取某个会话(群聊或单聊)的消息列表并投影出发言人/文本/时间", + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Agent-authored from the verified group/direct routing, stable projection, pagination, and optional safe resource workflow; it distinguishes conversation history from cross-conversation search and exact-ID mget." + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger." + } + ] + }, + "avoid_when": { + "value": [ + "要跨多个会话按关键词、发送者或消息类型检索时使用 +search-msg;已有一批精确消息 ID 时使用 +messages-mget" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified group/direct routing, stable projection, pagination, and optional safe resource workflow; it distinguishes conversation history from cross-conversation search and exact-ID mget.", + "candidates": [ + { + "value": [ + "要跨多个会话按关键词、发送者或消息类型检索时使用 +search-msg;已有一批精确消息 ID 时使用 +messages-mget" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Agent-authored from the verified group/direct routing, stable projection, pagination, and optional safe resource workflow; it distinguishes conversation history from cross-conversation search and exact-ID mget." + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger." + } + ] + }, + "effect": { + "value": "read", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "candidates": [ + { + "value": "read", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger." + } + ] + }, + "examples": { + "value": [ + "dws chat +chat-messages --group \u003copenConversationId\u003e --direction older", + "dws chat +chat-messages --open-dingtalk-id \u003copenDingTalkId\u003e --download-resources --output-dir ./downloads" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified group/direct routing, stable projection, pagination, and optional safe resource workflow; it distinguishes conversation history from cross-conversation search and exact-ID mget.", + "candidates": [ + { + "value": [ + "dws chat +chat-messages --group \u003copenConversationId\u003e --direction older", + "dws chat +chat-messages --open-dingtalk-id \u003copenDingTalkId\u003e --download-resources --output-dir ./downloads" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Agent-authored from the verified group/direct routing, stable projection, pagination, and optional safe resource workflow; it distinguishes conversation history from cross-conversation search and exact-ID mget." + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger." + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger." + } + ] + }, + "interface_reason": { + "value": "Reviewed built-in Shortcut adapter: it routes group or direct-message history reads, projects a stable message shape, and optionally orchestrates safe resource downloads with a failure ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "candidates": [ + { + "value": "Reviewed built-in Shortcut adapter: it routes group or direct-message history reads, projects a stable message shape, and optionally orchestrates safe resource downloads with a failure ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger." + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger." + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "Agent-authored from the verified group/direct routing, stable projection, pagination, and optional safe resource workflow; it distinguishes conversation history from cross-conversation search and exact-ID mget." + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger." + } + ] + }, + "use_when": { + "value": [ + "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified group/direct routing, stable projection, pagination, and optional safe resource workflow; it distinguishes conversation history from cross-conversation search and exact-ID mget.", + "candidates": [ + { + "value": [ + "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Agent-authored from the verified group/direct routing, stable projection, pagination, and optional safe resource workflow; it distinguishes conversation history from cross-conversation search and exact-ID mget." + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed built-in Shortcut adapter: it routes group or direct-message history reads, projects a stable message shape, and optionally orchestrates safe resource downloads with a failure ledger.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "ShortcutRegistry:chat +chat-messages", + "cobra-help:dws chat +chat-messages", + "internal/cli/schema_command_registry.json#chat.shortcut_chat_messages", + "internal/cli/schema_hints/metadata/chat.json", + "internal/cli/schema_hints/selection/chat.json" + ], + "use_when": [ + "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + ] + }, "chat +chat-mute": { "agent_summary": "全员禁言 / 取消全员禁言", "agent_summary_source": "dws-agent-selection/chat", @@ -9002,7 +9263,7 @@ }, "use_when": { "value": [ - "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" + "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" ], "source": "internal/cli/schema_hints/selection/chat.json", "precedence": "reviewed_explicit", @@ -9011,7 +9272,7 @@ "candidates": [ { "value": [ - "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" + "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" ], "source": "internal/cli/schema_hints/selection/chat.json", "precedence": "reviewed_explicit", @@ -9034,7 +9295,7 @@ "internal/cli/schema_hints/selection/chat.json" ], "use_when": [ - "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" + "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" ] }, "chat +messages-query-send-status": { @@ -9553,6 +9814,267 @@ "当你想知道自己发出的某条消息有哪些人已读/未读时使用;只读,需传会话 openConversationId 和该消息 openMessageId,可指定目标成员列表。" ] }, + "chat +messages-send": { + "agent_summary": "统一发送文本、Markdown、当前用户文件或已有 mediaId 图片", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "需要 bot/webhook 发送媒体、卡片或 thread 回复时不要假设等价支持;改用真实存在的专用下层命令,缺少下层能力时停止" + ], + "confirmation": "user_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws chat +messages-send --as user --chat-id \u003copenConversationId\u003e --markdown \"## 周报\" --idempotency-key \u003ckey\u003e", + "dws chat +messages-send --as user --user \u003cuserId\u003e --msg-type file --file ./report.pdf" + ], + "field_provenance": { + "agent_summary": { + "value": "统一发送文本、Markdown、当前用户文件或已有 mediaId 图片", + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified identity/content capability matrix, userId resolution, native local-file upload flow, and fail-closed rejection of unsupported bot/webhook media.", + "candidates": [ + { + "value": "统一发送文本、Markdown、当前用户文件或已有 mediaId 图片", + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Agent-authored from the verified identity/content capability matrix, userId resolution, native local-file upload flow, and fail-closed rejection of unsupported bot/webhook media." + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists." + } + ] + }, + "avoid_when": { + "value": [ + "需要 bot/webhook 发送媒体、卡片或 thread 回复时不要假设等价支持;改用真实存在的专用下层命令,缺少下层能力时停止" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified identity/content capability matrix, userId resolution, native local-file upload flow, and fail-closed rejection of unsupported bot/webhook media.", + "candidates": [ + { + "value": [ + "需要 bot/webhook 发送媒体、卡片或 thread 回复时不要假设等价支持;改用真实存在的专用下层命令,缺少下层能力时停止" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Agent-authored from the verified identity/content capability matrix, userId resolution, native local-file upload flow, and fail-closed rejection of unsupported bot/webhook media." + } + ] + }, + "confirmation": { + "value": "user_required", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "candidates": [ + { + "value": "user_required", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists." + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists." + } + ] + }, + "examples": { + "value": [ + "dws chat +messages-send --as user --chat-id \u003copenConversationId\u003e --markdown \"## 周报\" --idempotency-key \u003ckey\u003e", + "dws chat +messages-send --as user --user \u003cuserId\u003e --msg-type file --file ./report.pdf" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified identity/content capability matrix, userId resolution, native local-file upload flow, and fail-closed rejection of unsupported bot/webhook media.", + "candidates": [ + { + "value": [ + "dws chat +messages-send --as user --chat-id \u003copenConversationId\u003e --markdown \"## 周报\" --idempotency-key \u003ckey\u003e", + "dws chat +messages-send --as user --user \u003cuserId\u003e --msg-type file --file ./report.pdf" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Agent-authored from the verified identity/content capability matrix, userId resolution, native local-file upload flow, and fail-closed rejection of unsupported bot/webhook media." + } + ] + }, + "idempotency": { + "value": "unknown", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "candidates": [ + { + "value": "unknown", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists." + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists." + } + ] + }, + "interface_reason": { + "value": "Reviewed composite send adapter: it selects current-user, bot, or webhook transport; current-user additionally supports userId resolution, mediaId images, and the native init/upload/commit local-file flow.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "candidates": [ + { + "value": "Reviewed composite send adapter: it selects current-user, bot, or webhook transport; current-user additionally supports userId resolution, mediaId images, and the native init/upload/commit local-file flow.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists." + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists." + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "Agent-authored from the verified identity/content capability matrix, userId resolution, native local-file upload flow, and fail-closed rejection of unsupported bot/webhook media." + } + ] + }, + "risk": { + "value": "medium", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "candidates": [ + { + "value": "medium", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists." + } + ] + }, + "use_when": { + "value": [ + "当你希望用同一个入口选择 current-user、bot 或 webhook 身份发送消息时使用;命令会按身份校验目标、内容和凭据并路由到真实下层。current-user 支持文本/Markdown、已有 mediaId 图片、安全相对路径文件上传和幂等键;bot 支持群聊或批量单聊文本/Markdown;webhook 的目标由 token 所在群决定。不会把 user 文件能力伪装成 bot/webhook 等价能力。" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified identity/content capability matrix, userId resolution, native local-file upload flow, and fail-closed rejection of unsupported bot/webhook media.", + "candidates": [ + { + "value": [ + "当你希望用同一个入口选择 current-user、bot 或 webhook 身份发送消息时使用;命令会按身份校验目标、内容和凭据并路由到真实下层。current-user 支持文本/Markdown、已有 mediaId 图片、安全相对路径文件上传和幂等键;bot 支持群聊或批量单聊文本/Markdown;webhook 的目标由 token 所在群决定。不会把 user 文件能力伪装成 bot/webhook 等价能力。" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Agent-authored from the verified identity/content capability matrix, userId resolution, native local-file upload flow, and fail-closed rejection of unsupported bot/webhook media." + } + ] + } + }, + "idempotency": "unknown", + "interface_mode": "composite", + "interface_reason": "Reviewed composite send adapter: it selects current-user, bot, or webhook transport; current-user additionally supports userId resolution, mediaId images, and the native init/upload/commit local-file flow.", + "reviewed": true, + "risk": "medium", + "source_refs": [ + "ShortcutRegistry:chat +messages-send", + "cobra-help:dws chat +messages-send", + "internal/cli/schema_command_registry.json#chat.shortcut_messages_send", + "internal/cli/schema_hints/metadata/chat.json", + "internal/cli/schema_hints/selection/chat.json" + ], + "use_when": [ + "当你希望用同一个入口选择 current-user、bot 或 webhook 身份发送消息时使用;命令会按身份校验目标、内容和凭据并路由到真实下层。current-user 支持文本/Markdown、已有 mediaId 图片、安全相对路径文件上传和幂等键;bot 支持群聊或批量单聊文本/Markdown;webhook 的目标由 token 所在群决定。不会把 user 文件能力伪装成 bot/webhook 等价能力。" + ] + }, "chat +messages-send-by-webhook": { "agent_summary": "自定义机器人 Webhook 发送群消息", "agent_summary_source": "dws-agent-selection/chat", @@ -9811,6 +10333,267 @@ "当你只有自定义机器人的 Webhook token、想往其所在群推送消息时使用;会实际通过 Webhook 发群消息,需传 token、标题、正文,可 @手机号/userId 或 @所有人。" ] }, + "chat +messages-send-card": { + "agent_summary": "创建流式卡片,可在同一次调用中写入内容并结束", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "已有 bizId、只需要追加或更新现有卡片内容时使用 +messages-update-card" + ], + "confirmation": "user_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws chat +messages-send-card --group \u003copenConversationId\u003e --content \"任务已完成\"", + "dws chat +messages-send-card --receiver \u003cuserId\u003e" + ], + "field_provenance": { + "agent_summary": { + "value": "创建流式卡片,可在同一次调用中写入内容并结束", + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, two-action dry-run, bizId extraction, and partial-failure reporting.", + "candidates": [ + { + "value": "创建流式卡片,可在同一次调用中写入内容并结束", + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, two-action dry-run, bizId extraction, and partial-failure reporting." + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId." + } + ] + }, + "avoid_when": { + "value": [ + "已有 bizId、只需要追加或更新现有卡片内容时使用 +messages-update-card" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, two-action dry-run, bizId extraction, and partial-failure reporting.", + "candidates": [ + { + "value": [ + "已有 bizId、只需要追加或更新现有卡片内容时使用 +messages-update-card" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, two-action dry-run, bizId extraction, and partial-failure reporting." + } + ] + }, + "confirmation": { + "value": "user_required", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "candidates": [ + { + "value": "user_required", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId." + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId." + } + ] + }, + "examples": { + "value": [ + "dws chat +messages-send-card --group \u003copenConversationId\u003e --content \"任务已完成\"", + "dws chat +messages-send-card --receiver \u003cuserId\u003e" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, two-action dry-run, bizId extraction, and partial-failure reporting.", + "candidates": [ + { + "value": [ + "dws chat +messages-send-card --group \u003copenConversationId\u003e --content \"任务已完成\"", + "dws chat +messages-send-card --receiver \u003cuserId\u003e" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, two-action dry-run, bizId extraction, and partial-failure reporting." + } + ] + }, + "idempotency": { + "value": "unknown", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "candidates": [ + { + "value": "unknown", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId." + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId." + } + ] + }, + "interface_reason": { + "value": "Reviewed card lifecycle adapter: it can call create_and_send_card alone or compose it with update_streaming_card after extracting the returned bizId.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "candidates": [ + { + "value": "Reviewed card lifecycle adapter: it can call create_and_send_card alone or compose it with update_streaming_card after extracting the returned bizId.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId." + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId." + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, two-action dry-run, bizId extraction, and partial-failure reporting." + } + ] + }, + "risk": { + "value": "medium", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "candidates": [ + { + "value": "medium", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId." + } + ] + }, + "use_when": { + "value": [ + "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId 二选一。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, two-action dry-run, bizId extraction, and partial-failure reporting.", + "candidates": [ + { + "value": [ + "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId 二选一。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, two-action dry-run, bizId extraction, and partial-failure reporting." + } + ] + } + }, + "idempotency": "unknown", + "interface_mode": "composite", + "interface_reason": "Reviewed card lifecycle adapter: it can call create_and_send_card alone or compose it with update_streaming_card after extracting the returned bizId.", + "reviewed": true, + "risk": "medium", + "source_refs": [ + "ShortcutRegistry:chat +messages-send-card", + "cobra-help:dws chat +messages-send-card", + "internal/cli/schema_command_registry.json#chat.shortcut_messages_send_card", + "internal/cli/schema_hints/metadata/chat.json", + "internal/cli/schema_hints/selection/chat.json" + ], + "use_when": [ + "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId 二选一。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。" + ] + }, "chat +messages-update-card": { "agent_summary": "流式更新卡片内容(最后一次 --flow-status 应为 3)", "agent_summary_source": "dws-agent-selection/chat", @@ -10330,6 +11113,267 @@ "当你想快速看一眼自己都加入了哪些群、以及每个群的会话ID、名称、群主和人数,而不想翻分页或盯着原始返回时使用;内部分页拉取你加入的群列表,把每个群防御式地投影成 会话id / 名称 / 群主 / 人数 / 类型 等关键字段,输出成干净的结果。可选 --type 在本地按群类型过滤(底层接口本身不带类型参数,故为客户端过滤)。这是只读操作,不会改动任何群或成员关系。" ] }, + "chat +search-msg": { + "agent_summary": "多维搜索消息,可全量翻页并批量富化详情", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "只想读取一个已知会话的连续历史时使用 +chat-messages;已有精确消息 ID 时使用 +messages-mget" + ], + "confirmation": "not_required", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws chat +search-msg --query \"周报\" --senders \u003copenDingTalkId\u003e --days 3 --page-all", + "dws chat +search-msg --group \u003copenConversationId\u003e --message-type file --download-resources --output-dir ./downloads" + ], + "field_provenance": { + "agent_summary": { + "value": "多维搜索消息,可全量翻页并批量富化详情", + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified filter mapping, cursor pagination, batched mget enrichment, completeness ledger, and shared safe resource workflow.", + "candidates": [ + { + "value": "多维搜索消息,可全量翻页并批量富化详情", + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Agent-authored from the verified filter mapping, cursor pagination, batched mget enrichment, completeness ledger, and shared safe resource workflow." + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger." + } + ] + }, + "avoid_when": { + "value": [ + "只想读取一个已知会话的连续历史时使用 +chat-messages;已有精确消息 ID 时使用 +messages-mget" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified filter mapping, cursor pagination, batched mget enrichment, completeness ledger, and shared safe resource workflow.", + "candidates": [ + { + "value": [ + "只想读取一个已知会话的连续历史时使用 +chat-messages;已有精确消息 ID 时使用 +messages-mget" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Agent-authored from the verified filter mapping, cursor pagination, batched mget enrichment, completeness ledger, and shared safe resource workflow." + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger." + } + ] + }, + "effect": { + "value": "read", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "candidates": [ + { + "value": "read", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger." + } + ] + }, + "examples": { + "value": [ + "dws chat +search-msg --query \"周报\" --senders \u003copenDingTalkId\u003e --days 3 --page-all", + "dws chat +search-msg --group \u003copenConversationId\u003e --message-type file --download-resources --output-dir ./downloads" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified filter mapping, cursor pagination, batched mget enrichment, completeness ledger, and shared safe resource workflow.", + "candidates": [ + { + "value": [ + "dws chat +search-msg --query \"周报\" --senders \u003copenDingTalkId\u003e --days 3 --page-all", + "dws chat +search-msg --group \u003copenConversationId\u003e --message-type file --download-resources --output-dir ./downloads" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Agent-authored from the verified filter mapping, cursor pagination, batched mget enrichment, completeness ledger, and shared safe resource workflow." + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger." + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger." + } + ] + }, + "interface_reason": { + "value": "Reviewed search adapter: it combines filters, cursor pagination, batched mget enrichment, stable projection, completeness accounting, and optional safe resource downloads.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "candidates": [ + { + "value": "Reviewed search adapter: it combines filters, cursor pagination, batched mget enrichment, stable projection, completeness accounting, and optional safe resource downloads.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger." + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger." + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "Agent-authored from the verified filter mapping, cursor pagination, batched mget enrichment, completeness ledger, and shared safe resource workflow." + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger." + } + ] + }, + "use_when": { + "value": [ + "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified filter mapping, cursor pagination, batched mget enrichment, completeness ledger, and shared safe resource workflow.", + "candidates": [ + { + "value": [ + "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Agent-authored from the verified filter mapping, cursor pagination, batched mget enrichment, completeness ledger, and shared safe resource workflow." + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed search adapter: it combines filters, cursor pagination, batched mget enrichment, stable projection, completeness accounting, and optional safe resource downloads.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "ShortcutRegistry:chat +search-msg", + "cobra-help:dws chat +search-msg", + "internal/cli/schema_command_registry.json#chat.shortcut_search_msg", + "internal/cli/schema_hints/metadata/chat.json", + "internal/cli/schema_hints/selection/chat.json" + ], + "use_when": [ + "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。" + ] + }, "chat +send-to-group": { "agent_summary": "按群名直接给群发消息(自动搜群解析 openConversationId)", "agent_summary_source": "dws-agent-selection/chat", @@ -10588,6 +11632,267 @@ "当你只知道群的名字、想直接往这个群里发一条消息而不想先手动查群 ID 时使用;内部先按群名搜索群聊解析出唯一 openConversationId 再发送,群名匹配到多个群时会列出候选让你区分、绝不自行假定。会真实发出群消息。" ] }, + "chat +thread-replies": { + "agent_summary": "拉取某条话题消息的全部回复并投影出发言人/文本/时间", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "要回复 Thread 或发送新回复时不要使用此只读入口;当前没有经过验证的 thread writer Shortcut" + ], + "confirmation": "not_required", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws chat +thread-replies --group \u003copenConversationId\u003e --thread-id \u003cthreadId\u003e", + "dws chat +thread-replies --group \u003copenConversationId\u003e --topic-id \u003ctopicId\u003e --download-resources --output-dir ./downloads" + ], + "field_provenance": { + "agent_summary": { + "value": "拉取某条话题消息的全部回复并投影出发言人/文本/时间", + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified thread/topic alias, lower list_topic_replies mapping, stable projection, group-context fallback, and optional safe resource workflow.", + "candidates": [ + { + "value": "拉取某条话题消息的全部回复并投影出发言人/文本/时间", + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Agent-authored from the verified thread/topic alias, lower list_topic_replies mapping, stable projection, group-context fallback, and optional safe resource workflow." + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger." + } + ] + }, + "avoid_when": { + "value": [ + "要回复 Thread 或发送新回复时不要使用此只读入口;当前没有经过验证的 thread writer Shortcut" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified thread/topic alias, lower list_topic_replies mapping, stable projection, group-context fallback, and optional safe resource workflow.", + "candidates": [ + { + "value": [ + "要回复 Thread 或发送新回复时不要使用此只读入口;当前没有经过验证的 thread writer Shortcut" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Agent-authored from the verified thread/topic alias, lower list_topic_replies mapping, stable projection, group-context fallback, and optional safe resource workflow." + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger." + } + ] + }, + "effect": { + "value": "read", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "candidates": [ + { + "value": "read", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger." + } + ] + }, + "examples": { + "value": [ + "dws chat +thread-replies --group \u003copenConversationId\u003e --thread-id \u003cthreadId\u003e", + "dws chat +thread-replies --group \u003copenConversationId\u003e --topic-id \u003ctopicId\u003e --download-resources --output-dir ./downloads" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified thread/topic alias, lower list_topic_replies mapping, stable projection, group-context fallback, and optional safe resource workflow.", + "candidates": [ + { + "value": [ + "dws chat +thread-replies --group \u003copenConversationId\u003e --thread-id \u003cthreadId\u003e", + "dws chat +thread-replies --group \u003copenConversationId\u003e --topic-id \u003ctopicId\u003e --download-resources --output-dir ./downloads" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Agent-authored from the verified thread/topic alias, lower list_topic_replies mapping, stable projection, group-context fallback, and optional safe resource workflow." + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger." + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger." + } + ] + }, + "interface_reason": { + "value": "Reviewed thread reader adapter: it accepts threadId/topicId, reads lower topic replies, projects stable message fields, and optionally downloads reply resources safely.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "candidates": [ + { + "value": "Reviewed thread reader adapter: it accepts threadId/topicId, reads lower topic replies, projects stable message fields, and optionally downloads reply resources safely.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger." + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger." + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "Agent-authored from the verified thread/topic alias, lower list_topic_replies mapping, stable projection, group-context fallback, and optional safe resource workflow." + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger." + } + ] + }, + "use_when": { + "value": [ + "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified thread/topic alias, lower list_topic_replies mapping, stable projection, group-context fallback, and optional safe resource workflow.", + "candidates": [ + { + "value": [ + "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + ], + "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Agent-authored from the verified thread/topic alias, lower list_topic_replies mapping, stable projection, group-context fallback, and optional safe resource workflow." + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed thread reader adapter: it accepts threadId/topicId, reads lower topic replies, projects stable message fields, and optionally downloads reply resources safely.", + "reviewed": true, + "risk": "low", + "source_refs": [ + "ShortcutRegistry:chat +thread-replies", + "cobra-help:dws chat +thread-replies", + "internal/cli/schema_command_registry.json#chat.shortcut_thread_replies", + "internal/cli/schema_hints/metadata/chat.json", + "internal/cli/schema_hints/selection/chat.json" + ], + "use_when": [ + "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + ] + }, "chat +unread-chats": { "agent_summary": "列出我有未读消息的会话(投影会话名/未读数/会话ID)", "agent_summary_source": "dws-agent-selection/chat", diff --git a/internal/cli/schema_agent_metadata/index.json b/internal/cli/schema_agent_metadata/index.json index c09b5eed..81debe8b 100644 --- a/internal/cli/schema_agent_metadata/index.json +++ b/internal/cli/schema_agent_metadata/index.json @@ -1,17 +1,17 @@ { "version": 1, - "source_hash": "sha256:1fed2a8bc5328212fdf646c3e68d465a2a440b2d303c1ad120981e06c97da487", - "surface_hash": "sha256:4cf8460240b19f896c3a330c69982cbb5f57aa8576cdf30373172082eea893ed", + "source_hash": "sha256:ee1c5251720a1cefb87e97f98ee15bca1f929179c9d9a1c598ea5e0c7e281f95", + "surface_hash": "sha256:698ccdc961c0b745e6524e33ae13d87ef4fcac012a2e833c7a727a79d35004db", "coverage": { "surface_products": 26, "products_with_metadata": 26, - "surface_tools": 813, - "tools_with_metadata": 813, - "tools_with_agent_summary": 813, - "tools_with_use_when": 813, - "tools_with_avoid_when": 813, - "tools_with_examples": 813, - "tools_with_interface_mode": 813, + "surface_tools": 818, + "tools_with_metadata": 818, + "tools_with_agent_summary": 818, + "tools_with_use_when": 818, + "tools_with_avoid_when": 818, + "tools_with_examples": 818, + "tools_with_interface_mode": 818, "unmatched_skill_tools": 122, "unreviewed_skill_tools": 11 }, diff --git a/internal/cli/schema_agent_metadata_audit.json b/internal/cli/schema_agent_metadata_audit.json index 625f7155..d66234fe 100644 --- a/internal/cli/schema_agent_metadata_audit.json +++ b/internal/cli/schema_agent_metadata_audit.json @@ -1,11 +1,11 @@ { "version": 1, - "source_hash": "sha256:1fed2a8bc5328212fdf646c3e68d465a2a440b2d303c1ad120981e06c97da487", - "surface_hash": "sha256:4cf8460240b19f896c3a330c69982cbb5f57aa8576cdf30373172082eea893ed", + "source_hash": "sha256:ee1c5251720a1cefb87e97f98ee15bca1f929179c9d9a1c598ea5e0c7e281f95", + "surface_hash": "sha256:698ccdc961c0b745e6524e33ae13d87ef4fcac012a2e833c7a727a79d35004db", "source_files": 160, "hint_files": 54, "hint_products": 43, - "hint_tools": 1778, + "hint_tools": 1788, "interface_metadata": { "source": "mcp-tools-list+cli-registry", "revision": "4574f7022c32cf4c033e9b7b4156e2fec815fed8", @@ -29,13 +29,13 @@ "coverage": { "surface_products": 26, "products_with_metadata": 26, - "surface_tools": 813, - "tools_with_metadata": 813, - "tools_with_agent_summary": 813, - "tools_with_use_when": 813, - "tools_with_avoid_when": 813, - "tools_with_examples": 813, - "tools_with_interface_mode": 813, + "surface_tools": 818, + "tools_with_metadata": 818, + "tools_with_agent_summary": 818, + "tools_with_use_when": 818, + "tools_with_avoid_when": 818, + "tools_with_examples": 818, + "tools_with_interface_mode": 818, "unmatched_skill_tools": 122, "unreviewed_skill_tools": 11 }, diff --git a/internal/cli/schema_catalog/catalog.json b/internal/cli/schema_catalog/catalog.json index d281439a..38f2006f 100644 --- a/internal/cli/schema_catalog/catalog.json +++ b/internal/cli/schema_catalog/catalog.json @@ -1,17 +1,17 @@ { "version": 1, - "surface_hash": "sha256:4cf8460240b19f896c3a330c69982cbb5f57aa8576cdf30373172082eea893ed", - "source_hash": "sha256:a933860d7efdc55d1868cb1efe8ff56c493a48cbffae246e2013f4394e70259a", + "surface_hash": "sha256:698ccdc961c0b745e6524e33ae13d87ef4fcac012a2e833c7a727a79d35004db", + "source_hash": "sha256:7e52ceb70627216cc13cdbad03cb5daec57c03d03cc7f854ed06cadfa984e015", "catalog": { "agent_metadata": { "products_with_metadata": 26, "source": "embedded-skill-metadata", - "source_hash": "sha256:1fed2a8bc5328212fdf646c3e68d465a2a440b2d303c1ad120981e06c97da487", - "surface_hash": "sha256:4cf8460240b19f896c3a330c69982cbb5f57aa8576cdf30373172082eea893ed", + "source_hash": "sha256:ee1c5251720a1cefb87e97f98ee15bca1f929179c9d9a1c598ea5e0c7e281f95", + "surface_hash": "sha256:698ccdc961c0b745e6524e33ae13d87ef4fcac012a2e833c7a727a79d35004db", "surface_products": 26, - "surface_tools": 813, - "tools_with_agent_summary": 813, - "tools_with_metadata": 813, + "surface_tools": 818, + "tools_with_agent_summary": 818, + "tools_with_metadata": 818, "unmatched_skill_tools": 122, "version": 1 }, @@ -7722,7 +7722,7 @@ "id": "chat", "name": "群聊 / 消息 / 机器人", "runtime": true, - "tool_count": 120, + "tool_count": 125, "tools": [ { "agent_metadata_source": "embedded-skill-metadata", @@ -9719,7 +9719,7 @@ "cli_name": "+at-me", "cli_path": "chat +at-me", "confirmation": "not_required", - "description": "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。", + "description": "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。\n\n参数约束:\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", "effect": "read", "idempotency": "idempotent", "interface_mode": "composite", @@ -10071,6 +10071,32 @@ "当你想查看自己作为群主或管理员在管理哪些群时使用;只读分页返回,可用 --role OWNER/ADMIN 按角色过滤。" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "拉取某个会话(群聊或单聊)的消息列表并投影出发言人/文本/时间", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "要跨多个会话按关键词、发送者或消息类型检索时使用 +search-msg;已有一批精确消息 ID 时使用 +messages-mget" + ], + "canonical_path": "chat.shortcut_chat_messages", + "cli_name": "+chat-messages", + "cli_path": "chat +chat-messages", + "confirmation": "not_required", + "description": "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。\n\n参数约束:\n - --group、--conversation-id、--id、--user、--open-dingtalk-id 必须且只能指定一个\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", + "effect": "read", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed built-in Shortcut adapter: it routes group or direct-message history reads, projects a stable message shape, and optionally orchestrates safe resource downloads with a failure ledger.", + "name": "shortcut_chat_messages", + "primary_cli_path": "chat +chat-messages", + "reviewed": true, + "risk": "low", + "title": "拉取某个会话(群聊或单聊)的消息列表并投影出发言人/文本/时间", + "use_when": [ + "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "全员禁言 / 取消全员禁言", @@ -10603,7 +10629,7 @@ "cli_name": "+messages-mget", "cli_path": "chat +messages-mget", "confirmation": "not_required", - "description": "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。\n\n参数约束:\n - --msg-ids 去重后必须包含 1-50 条消息 ID\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", + "description": "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。\n\n参数约束:\n - --msg-ids 去重后必须包含 1-50 条消息 ID\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", "effect": "read", "idempotency": "idempotent", "interface_mode": "composite", @@ -10614,7 +10640,7 @@ "risk": "low", "title": "根据消息 ID 批量查询消息(最多 50 条)", "use_when": [ - "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" + "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" ] }, { @@ -10669,6 +10695,32 @@ "当你想知道自己发出的某条消息有哪些人已读/未读时使用;只读,需传会话 openConversationId 和该消息 openMessageId,可指定目标成员列表。" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "统一发送文本、Markdown、当前用户文件或已有 mediaId 图片", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "需要 bot/webhook 发送媒体、卡片或 thread 回复时不要假设等价支持;改用真实存在的专用下层命令,缺少下层能力时停止" + ], + "canonical_path": "chat.shortcut_messages_send", + "cli_name": "+messages-send", + "cli_path": "chat +messages-send", + "confirmation": "user_required", + "description": "当你希望用同一个入口选择 current-user、bot 或 webhook 身份发送消息时使用;命令会按身份校验目标、内容和凭据并路由到真实下层。current-user 支持文本/Markdown、已有 mediaId 图片、安全相对路径文件上传和幂等键;bot 支持群聊或批量单聊文本/Markdown;webhook 的目标由 token 所在群决定。不会把 user 文件能力伪装成 bot/webhook 等价能力。\n\n参数约束:\n - --text、--markdown、--media-id、--file、--file-path 至少指定一个\n - --text、--markdown、--media-id、--file、--file-path 互斥,最多指定一个\n - --identity、--as 互斥,最多指定一个\n - --group、--chat-id 互斥,最多指定一个\n - --user、--open-dingtalk-id 互斥,最多指定一个\n - --uuid、--idempotency-key 互斥,最多指定一个\n - 身份相关目标、内容和凭据不能为空,完整能力矩阵见命令说明", + "effect": "write", + "idempotency": "unknown", + "interface_mode": "composite", + "interface_reason": "Reviewed composite send adapter: it selects current-user, bot, or webhook transport; current-user additionally supports userId resolution, mediaId images, and the native init/upload/commit local-file flow.", + "name": "shortcut_messages_send", + "primary_cli_path": "chat +messages-send", + "reviewed": true, + "risk": "medium", + "title": "统一发送文本、Markdown、当前用户文件或已有 mediaId 图片", + "use_when": [ + "当你希望用同一个入口选择 current-user、bot 或 webhook 身份发送消息时使用;命令会按身份校验目标、内容和凭据并路由到真实下层。current-user 支持文本/Markdown、已有 mediaId 图片、安全相对路径文件上传和幂等键;bot 支持群聊或批量单聊文本/Markdown;webhook 的目标由 token 所在群决定。不会把 user 文件能力伪装成 bot/webhook 等价能力。" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "自定义机器人 Webhook 发送群消息", @@ -10695,6 +10747,32 @@ "当你只有自定义机器人的 Webhook token、想往其所在群推送消息时使用;会实际通过 Webhook 发群消息,需传 token、标题、正文,可 @手机号/userId 或 @所有人。" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "创建流式卡片,可在同一次调用中写入内容并结束", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "已有 bizId、只需要追加或更新现有卡片内容时使用 +messages-update-card" + ], + "canonical_path": "chat.shortcut_messages_send_card", + "cli_name": "+messages-send-card", + "cli_path": "chat +messages-send-card", + "confirmation": "user_required", + "description": "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId 二选一。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。\n\n参数约束:\n - --group、--receiver 必须且只能指定一个\n - --flow-status 必须在 1-5 之间,且显式指定时必须同时提供 --content", + "effect": "write", + "idempotency": "unknown", + "interface_mode": "composite", + "interface_reason": "Reviewed card lifecycle adapter: it can call create_and_send_card alone or compose it with update_streaming_card after extracting the returned bizId.", + "name": "shortcut_messages_send_card", + "primary_cli_path": "chat +messages-send-card", + "reviewed": true, + "risk": "medium", + "title": "创建流式卡片,可在同一次调用中写入内容并结束", + "use_when": [ + "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId 二选一。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "流式更新卡片内容(最后一次 --flow-status 应为 3)", @@ -10747,6 +10825,32 @@ "当你想快速看一眼自己都加入了哪些群、以及每个群的会话ID、名称、群主和人数,而不想翻分页或盯着原始返回时使用;内部分页拉取你加入的群列表,把每个群防御式地投影成 会话id / 名称 / 群主 / 人数 / 类型 等关键字段,输出成干净的结果。可选 --type 在本地按群类型过滤(底层接口本身不带类型参数,故为客户端过滤)。这是只读操作,不会改动任何群或成员关系。" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "多维搜索消息,可全量翻页并批量富化详情", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "只想读取一个已知会话的连续历史时使用 +chat-messages;已有精确消息 ID 时使用 +messages-mget" + ], + "canonical_path": "chat.shortcut_search_msg", + "cli_name": "+search-msg", + "cli_path": "chat +search-msg", + "confirmation": "not_required", + "description": "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。\n\n参数约束:\n - 至少指定一个内容、身份、会话或消息类型过滤条件\n - 需与 --end 一起传\n - 需与 --start 一起传\n - --groups、--chat-id 互斥,最多指定一个\n - --senders、--sender 互斥,最多指定一个\n - --at-me、--is-at-me 互斥,最多指定一个\n - --conversation-type、--chat-type 互斥,最多指定一个\n - --limit、--page-size 互斥,最多指定一个\n - --cursor、--page-token 互斥,最多指定一个\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", + "effect": "read", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed search adapter: it combines filters, cursor pagination, batched mget enrichment, stable projection, completeness accounting, and optional safe resource downloads.", + "name": "shortcut_search_msg", + "primary_cli_path": "chat +search-msg", + "reviewed": true, + "risk": "low", + "title": "多维搜索消息,可全量翻页并批量富化详情", + "use_when": [ + "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "按群名直接给群发消息(自动搜群解析 openConversationId)", @@ -10773,6 +10877,32 @@ "当你只知道群的名字、想直接往这个群里发一条消息而不想先手动查群 ID 时使用;内部先按群名搜索群聊解析出唯一 openConversationId 再发送,群名匹配到多个群时会列出候选让你区分、绝不自行假定。会真实发出群消息。" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "拉取某条话题消息的全部回复并投影出发言人/文本/时间", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "要回复 Thread 或发送新回复时不要使用此只读入口;当前没有经过验证的 thread writer Shortcut" + ], + "canonical_path": "chat.shortcut_thread_replies", + "cli_name": "+thread-replies", + "cli_path": "chat +thread-replies", + "confirmation": "not_required", + "description": "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。\n\n参数约束:\n - --thread-id 与兼容参数 --topic-id 必须且只能指定一个\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", + "effect": "read", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed thread reader adapter: it accepts threadId/topicId, reads lower topic replies, projects stable message fields, and optionally downloads reply resources safely.", + "name": "shortcut_thread_replies", + "primary_cli_path": "chat +thread-replies", + "reviewed": true, + "risk": "low", + "title": "拉取某条话题消息的全部回复并投影出发言人/文本/时间", + "use_when": [ + "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "列出我有未读消息的会话(投影会话名/未读数/会话ID)", @@ -25785,6 +25915,6 @@ } ], "source": "embedded-command-catalog", - "tool_count": 813 + "tool_count": 818 } } diff --git a/internal/cli/schema_catalog/tools/chat.json b/internal/cli/schema_catalog/tools/chat.json index b7c65806..6e10ae9b 100644 --- a/internal/cli/schema_catalog/tools/chat.json +++ b/internal/cli/schema_catalog/tools/chat.json @@ -50914,7 +50914,7 @@ "cli_name": "+at-me", "cli_path": "chat +at-me", "confirmation": "not_required", - "description": "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。", + "description": "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。\n\n参数约束:\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", "display": "群聊 / 消息 / 机器人", "effect": "read", "effect_source": "agent-hint", @@ -51013,13 +51013,13 @@ "precedence": "cobra_help", "selected": true, "source": "cobra_help", - "value": "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。" + "value": "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。\n\n参数约束:\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" } ], "precedence": "cobra_help", "resolution": "highest_precedence", "source": "cobra_help", - "value": "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。" + "value": "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。\n\n参数约束:\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" }, "effect": { "candidates": [ @@ -51203,7 +51203,7 @@ "interface_reason": "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.", "is_alias": false, "name": "shortcut_at_me", - "parameter_count": 4, + "parameter_count": 7, "parameters": { "cursor": { "default": "0", @@ -51387,6 +51387,90 @@ "required": false, "type": "integer" }, + "download-resources": { + "description": "自动下载消息中的全部可识别 mediaId/fileId 资源", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "自动下载消息中的全部可识别 mediaId/fileId 资源" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "自动下载消息中的全部可识别 mediaId/fileId 资源" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "downloadResources" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "downloadResources" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "property": "downloadResources", + "required": false, + "type": "boolean" + }, "limit": { "default": "50", "description": "每页返回数量(默认 50)", @@ -51567,6 +51651,187 @@ "property": "noReactions", "required": false, "type": "boolean" + }, + "output-dir": { + "default": "./downloads", + "description": "资源输出目录;必须是工作目录内的相对路径,禁止绝对路径和 .. 逃逸", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "资源输出目录;必须是工作目录内的相对路径,禁止绝对路径和 .. 逃逸" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "资源输出目录;必须是工作目录内的相对路径,禁止绝对路径和 .. 逃逸" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "outputDir" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "outputDir" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "outputDir", + "required": false, + "type": "string" + }, + "overwrite": { + "description": "允许覆盖同名资源文件(默认拒绝)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "允许覆盖同名资源文件(默认拒绝)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "允许覆盖同名资源文件(默认拒绝)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "overwrite" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "overwrite" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "property": "overwrite", + "required": false, + "type": "boolean" } }, "path": "chat.shortcut_at_me", @@ -57921,6 +58186,1251 @@ "当你想查看自己作为群主或管理员在管理哪些群时使用;只读分页返回,可用 --role OWNER/ADMIN 按角色过滤。" ] }, + "chat.shortcut_chat_messages": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "ShortcutRegistry:chat +chat-messages", + "cobra-help:dws chat +chat-messages", + "internal/cli/schema_command_registry.json#chat.shortcut_chat_messages", + "internal/cli/schema_hints/metadata/chat.json", + "internal/cli/schema_hints/selection/chat.json" + ], + "agent_summary": "拉取某个会话(群聊或单聊)的消息列表并投影出发言人/文本/时间", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "要跨多个会话按关键词、发送者或消息类型检索时使用 +search-msg;已有一批精确消息 ID 时使用 +messages-mget" + ], + "canonical_path": "chat.shortcut_chat_messages", + "cli_name": "+chat-messages", + "cli_path": "chat +chat-messages", + "confirmation": "not_required", + "constraints": { + "mutually_exclusive": [ + [ + "group", + "user", + "open-dingtalk-id" + ] + ], + "require_one_of": [ + [ + "group", + "user", + "open-dingtalk-id" + ] + ] + }, + "description": "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。\n\n参数约束:\n - --group、--conversation-id、--id、--user、--open-dingtalk-id 必须且只能指定一个\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", + "display": "群聊 / 消息 / 机器人", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws chat +chat-messages --group \u003copenConversationId\u003e --direction older", + "dws chat +chat-messages --open-dingtalk-id \u003copenDingTalkId\u003e --download-resources --output-dir ./downloads" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified group/direct routing, stable projection, pagination, and optional safe resource workflow; it distinguishes conversation history from cross-conversation search and exact-ID mget.", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": "拉取某个会话(群聊或单聊)的消息列表并投影出发言人/文本/时间" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified group/direct routing, stable projection, pagination, and optional safe resource workflow; it distinguishes conversation history from cross-conversation search and exact-ID mget.", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": "拉取某个会话(群聊或单聊)的消息列表并投影出发言人/文本/时间" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified group/direct routing, stable projection, pagination, and optional safe resource workflow; it distinguishes conversation history from cross-conversation search and exact-ID mget.", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "要跨多个会话按关键词、发送者或消息类型检索时使用 +search-msg;已有一批精确消息 ID 时使用 +messages-mget" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified group/direct routing, stable projection, pagination, and optional safe resource workflow; it distinguishes conversation history from cross-conversation search and exact-ID mget.", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "要跨多个会话按关键词、发送者或消息类型检索时使用 +search-msg;已有一批精确消息 ID 时使用 +messages-mget" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "chat +chat-messages", + "value": "chat.shortcut_chat_messages" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "chat +chat-messages", + "value": "chat.shortcut_chat_messages" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。\n\n参数约束:\n - --group、--conversation-id、--id、--user、--open-dingtalk-id 必须且只能指定一个\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。\n\n参数约束:\n - --group、--conversation-id、--id、--user、--open-dingtalk-id 必须且只能指定一个\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "read" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "read" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified group/direct routing, stable projection, pagination, and optional safe resource workflow; it distinguishes conversation history from cross-conversation search and exact-ID mget.", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "dws chat +chat-messages --group \u003copenConversationId\u003e --direction older", + "dws chat +chat-messages --open-dingtalk-id \u003copenDingTalkId\u003e --download-resources --output-dir ./downloads" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified group/direct routing, stable projection, pagination, and optional safe resource workflow; it distinguishes conversation history from cross-conversation search and exact-ID mget.", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "dws chat +chat-messages --group \u003copenConversationId\u003e --direction older", + "dws chat +chat-messages --open-dingtalk-id \u003copenDingTalkId\u003e --download-resources --output-dir ./downloads" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "Reviewed built-in Shortcut adapter: it routes group or direct-message history reads, projects a stable message shape, and optionally orchestrates safe resource downloads with a failure ledger." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "Reviewed built-in Shortcut adapter: it routes group or direct-message history reads, projects a stable message shape, and optionally orchestrates safe resource downloads with a failure ledger." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified group/direct routing, stable projection, pagination, and optional safe resource workflow; it distinguishes conversation history from cross-conversation search and exact-ID mget.", + "selected": false, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "拉取某个会话(群聊或单聊)的消息列表并投影出发言人/文本/时间" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "拉取某个会话(群聊或单聊)的消息列表并投影出发言人/文本/时间" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified group/direct routing, stable projection, pagination, and optional safe resource workflow; it distinguishes conversation history from cross-conversation search and exact-ID mget.", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified group/direct routing, stable projection, pagination, and optional safe resource workflow; it distinguishes conversation history from cross-conversation search and exact-ID mget.", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + ] + } + }, + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed built-in Shortcut adapter: it routes group or direct-message history reads, projects a stable message shape, and optionally orchestrates safe resource downloads with a failure ledger.", + "is_alias": false, + "name": "shortcut_chat_messages", + "parameter_count": 10, + "parameters": { + "direction": { + "description": "时间方向 newer/older;省略时为 older,从时间边界向前读取(可选值: newer, older)", + "enum": [ + "newer", + "older" + ], + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "时间方向 newer/older;省略时为 older,从时间边界向前读取(可选值: newer, older)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "时间方向 newer/older;省略时为 older,从时间边界向前读取(可选值: newer, older)" + }, + "enum": { + "candidates": [ + { + "precedence": "native_annotation", + "selected": true, + "source": "native_annotation", + "value": [ + "newer", + "older" + ] + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": [] + } + ], + "precedence": "native_annotation", + "resolution": "highest_precedence", + "source": "native_annotation", + "value": [ + "newer", + "older" + ] + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "direction" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "direction" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "direction", + "required": false, + "type": "string" + }, + "download-resources": { + "description": "自动下载消息中的全部可识别 mediaId/fileId 资源", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "自动下载消息中的全部可识别 mediaId/fileId 资源" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "自动下载消息中的全部可识别 mediaId/fileId 资源" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "downloadResources" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "downloadResources" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "property": "downloadResources", + "required": false, + "type": "boolean" + }, + "group": { + "description": "群会话 ID(openConversationId),与 --user 互斥", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "群会话 ID(openConversationId),与 --user 互斥" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "群会话 ID(openConversationId),与 --user 互斥" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "group" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "group" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "group", + "required": false, + "type": "string" + }, + "limit": { + "description": "每页拉取的消息条数(可选)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "每页拉取的消息条数(可选)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "每页拉取的消息条数(可选)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "limit" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "limit" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "integer" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "integer" + } + }, + "property": "limit", + "required": false, + "type": "integer" + }, + "no-reactions": { + "description": "不输出消息 reaction(默认输出)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "不输出消息 reaction(默认输出)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "不输出消息 reaction(默认输出)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "noReactions" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "noReactions" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "property": "noReactions", + "required": false, + "type": "boolean" + }, + "open-dingtalk-id": { + "description": "单聊对方的 openDingTalkId,与 --group/--user 互斥", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "单聊对方的 openDingTalkId,与 --group/--user 互斥" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "单聊对方的 openDingTalkId,与 --group/--user 互斥" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "openDingtalkId" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "openDingtalkId" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "openDingtalkId", + "required": false, + "type": "string" + }, + "output-dir": { + "default": "./downloads", + "description": "资源输出目录;必须是工作目录内的相对路径,禁止绝对路径和 .. 逃逸", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "资源输出目录;必须是工作目录内的相对路径,禁止绝对路径和 .. 逃逸" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "资源输出目录;必须是工作目录内的相对路径,禁止绝对路径和 .. 逃逸" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "outputDir" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "outputDir" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "outputDir", + "required": false, + "type": "string" + }, + "overwrite": { + "description": "允许覆盖同名资源文件(默认拒绝)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "允许覆盖同名资源文件(默认拒绝)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "允许覆盖同名资源文件(默认拒绝)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "overwrite" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "overwrite" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "property": "overwrite", + "required": false, + "type": "boolean" + }, + "time": { + "description": "时间边界,如 \"2025-03-01 00:00:00\";省略时从当前时间向前读取最近消息", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "时间边界,如 \"2025-03-01 00:00:00\";省略时从当前时间向前读取最近消息" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "时间边界,如 \"2025-03-01 00:00:00\";省略时从当前时间向前读取最近消息" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "time" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "time" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "time", + "required": false, + "type": "string" + }, + "user": { + "description": "单聊对方的 userId,与 --group 互斥", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "单聊对方的 userId,与 --group 互斥" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "单聊对方的 userId,与 --group 互斥" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "user" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "user" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "user", + "required": false, + "type": "string" + } + }, + "path": "chat.shortcut_chat_messages", + "primary_cli_path": "chat +chat-messages", + "product_id": "chat", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "拉取某个会话(群聊或单聊)的消息列表并投影出发言人/文本/时间", + "use_when": [ + "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + ] + }, "chat.shortcut_chat_mute": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ @@ -68942,7 +70452,7 @@ "cli_name": "+messages-mget", "cli_path": "chat +messages-mget", "confirmation": "not_required", - "description": "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。\n\n参数约束:\n - --msg-ids 去重后必须包含 1-50 条消息 ID\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", + "description": "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。\n\n参数约束:\n - --msg-ids 去重后必须包含 1-50 条消息 ID\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", "display": "群聊 / 消息 / 机器人", "effect": "read", "effect_source": "agent-hint", @@ -69040,13 +70550,13 @@ "precedence": "cobra_help", "selected": true, "source": "cobra_help", - "value": "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。\n\n参数约束:\n - --msg-ids 去重后必须包含 1-50 条消息 ID\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" + "value": "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。\n\n参数约束:\n - --msg-ids 去重后必须包含 1-50 条消息 ID\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" } ], "precedence": "cobra_help", "resolution": "highest_precedence", "source": "cobra_help", - "value": "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。\n\n参数约束:\n - --msg-ids 去重后必须包含 1-50 条消息 ID\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" + "value": "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。\n\n参数约束:\n - --msg-ids 去重后必须包含 1-50 条消息 ID\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" }, "effect": { "candidates": [ @@ -69209,7 +70719,7 @@ "selected": true, "source": "internal/cli/schema_hints/selection/chat.json", "value": [ - "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" + "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" ] } ], @@ -69218,7 +70728,7 @@ "review_reason": "Reviewed against the built-in Shortcut, executable Cobra flags, safe relative-path download contract, and per-resource failure ledger; this selection update advertises the new optional resource workflow without changing its runtime safety facts.", "source": "internal/cli/schema_hints/selection/chat.json", "value": [ - "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" + "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" ] } }, @@ -69231,7 +70741,7 @@ "parameter_count": 5, "parameters": { "download-resources": { - "description": "自动下载消息中的全部可识别 mediaId 资源", + "description": "自动下载消息中的全部可识别 mediaId/fileId 资源", "field_provenance": { "description": { "candidates": [ @@ -69239,7 +70749,7 @@ "precedence": "cobra_contract", "selected": true, "source": "cobra_usage", - "value": "自动下载消息中的全部可识别 mediaId 资源" + "value": "自动下载消息中的全部可识别 mediaId/fileId 资源" }, { "precedence": "default", @@ -69251,7 +70761,7 @@ "precedence": "cobra_contract", "resolution": "highest_precedence", "source": "cobra_usage", - "value": "自动下载消息中的全部可识别 mediaId 资源" + "value": "自动下载消息中的全部可识别 mediaId/fileId 资源" }, "property": { "candidates": [ @@ -69502,7 +71012,7 @@ }, "output-dir": { "default": "./downloads", - "description": "资源输出目录;--output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", + "description": "资源输出目录;必须是工作目录内的相对路径,禁止绝对路径和 .. 逃逸", "field_provenance": { "description": { "candidates": [ @@ -69510,7 +71020,7 @@ "precedence": "cobra_contract", "selected": true, "source": "cobra_usage", - "value": "资源输出目录;--output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" + "value": "资源输出目录;必须是工作目录内的相对路径,禁止绝对路径和 .. 逃逸" }, { "precedence": "default", @@ -69522,7 +71032,7 @@ "precedence": "cobra_contract", "resolution": "highest_precedence", "source": "cobra_usage", - "value": "资源输出目录;--output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" + "value": "资源输出目录;必须是工作目录内的相对路径,禁止绝对路径和 .. 逃逸" }, "property": { "candidates": [ @@ -69690,7 +71200,7 @@ "source": "reviewed_command_registry", "title": "根据消息 ID 批量查询消息(最多 50 条)", "use_when": [ - "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" + "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" ] }, "chat.shortcut_messages_query_send_status": { @@ -70697,6 +72207,2532 @@ "当你想知道自己发出的某条消息有哪些人已读/未读时使用;只读,需传会话 openConversationId 和该消息 openMessageId,可指定目标成员列表。" ] }, + "chat.shortcut_messages_send": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "ShortcutRegistry:chat +messages-send", + "cobra-help:dws chat +messages-send", + "internal/cli/schema_command_registry.json#chat.shortcut_messages_send", + "internal/cli/schema_hints/metadata/chat.json", + "internal/cli/schema_hints/selection/chat.json" + ], + "agent_summary": "统一发送文本、Markdown、当前用户文件或已有 mediaId 图片", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "需要 bot/webhook 发送媒体、卡片或 thread 回复时不要假设等价支持;改用真实存在的专用下层命令,缺少下层能力时停止" + ], + "canonical_path": "chat.shortcut_messages_send", + "cli_name": "+messages-send", + "cli_path": "chat +messages-send", + "confirmation": "user_required", + "constraints": { + "mutually_exclusive": [ + [ + "text", + "markdown", + "media-id", + "file", + "file-path" + ], + [ + "identity", + "as" + ], + [ + "group", + "chat-id" + ], + [ + "user", + "open-dingtalk-id" + ], + [ + "uuid", + "idempotency-key" + ] + ], + "require_one_of": [ + [ + "text", + "markdown", + "media-id", + "file", + "file-path" + ] + ] + }, + "description": "当你希望用同一个入口选择 current-user、bot 或 webhook 身份发送消息时使用;命令会按身份校验目标、内容和凭据并路由到真实下层。current-user 支持文本/Markdown、已有 mediaId 图片、安全相对路径文件上传和幂等键;bot 支持群聊或批量单聊文本/Markdown;webhook 的目标由 token 所在群决定。不会把 user 文件能力伪装成 bot/webhook 等价能力。\n\n参数约束:\n - --text、--markdown、--media-id、--file、--file-path 至少指定一个\n - --text、--markdown、--media-id、--file、--file-path 互斥,最多指定一个\n - --identity、--as 互斥,最多指定一个\n - --group、--chat-id 互斥,最多指定一个\n - --user、--open-dingtalk-id 互斥,最多指定一个\n - --uuid、--idempotency-key 互斥,最多指定一个\n - 身份相关目标、内容和凭据不能为空,完整能力矩阵见命令说明", + "display": "群聊 / 消息 / 机器人", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws chat +messages-send --as user --chat-id \u003copenConversationId\u003e --markdown \"## 周报\" --idempotency-key \u003ckey\u003e", + "dws chat +messages-send --as user --user \u003cuserId\u003e --msg-type file --file ./report.pdf" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified identity/content capability matrix, userId resolution, native local-file upload flow, and fail-closed rejection of unsupported bot/webhook media.", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": "统一发送文本、Markdown、当前用户文件或已有 mediaId 图片" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified identity/content capability matrix, userId resolution, native local-file upload flow, and fail-closed rejection of unsupported bot/webhook media.", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": "统一发送文本、Markdown、当前用户文件或已有 mediaId 图片" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified identity/content capability matrix, userId resolution, native local-file upload flow, and fail-closed rejection of unsupported bot/webhook media.", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "需要 bot/webhook 发送媒体、卡片或 thread 回复时不要假设等价支持;改用真实存在的专用下层命令,缺少下层能力时停止" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified identity/content capability matrix, userId resolution, native local-file upload flow, and fail-closed rejection of unsupported bot/webhook media.", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "需要 bot/webhook 发送媒体、卡片或 thread 回复时不要假设等价支持;改用真实存在的专用下层命令,缺少下层能力时停止" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "chat +messages-send", + "value": "chat.shortcut_messages_send" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "chat +messages-send", + "value": "chat.shortcut_messages_send" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "user_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "user_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "当你希望用同一个入口选择 current-user、bot 或 webhook 身份发送消息时使用;命令会按身份校验目标、内容和凭据并路由到真实下层。current-user 支持文本/Markdown、已有 mediaId 图片、安全相对路径文件上传和幂等键;bot 支持群聊或批量单聊文本/Markdown;webhook 的目标由 token 所在群决定。不会把 user 文件能力伪装成 bot/webhook 等价能力。\n\n参数约束:\n - --text、--markdown、--media-id、--file、--file-path 至少指定一个\n - --text、--markdown、--media-id、--file、--file-path 互斥,最多指定一个\n - --identity、--as 互斥,最多指定一个\n - --group、--chat-id 互斥,最多指定一个\n - --user、--open-dingtalk-id 互斥,最多指定一个\n - --uuid、--idempotency-key 互斥,最多指定一个\n - 身份相关目标、内容和凭据不能为空,完整能力矩阵见命令说明" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "当你希望用同一个入口选择 current-user、bot 或 webhook 身份发送消息时使用;命令会按身份校验目标、内容和凭据并路由到真实下层。current-user 支持文本/Markdown、已有 mediaId 图片、安全相对路径文件上传和幂等键;bot 支持群聊或批量单聊文本/Markdown;webhook 的目标由 token 所在群决定。不会把 user 文件能力伪装成 bot/webhook 等价能力。\n\n参数约束:\n - --text、--markdown、--media-id、--file、--file-path 至少指定一个\n - --text、--markdown、--media-id、--file、--file-path 互斥,最多指定一个\n - --identity、--as 互斥,最多指定一个\n - --group、--chat-id 互斥,最多指定一个\n - --user、--open-dingtalk-id 互斥,最多指定一个\n - --uuid、--idempotency-key 互斥,最多指定一个\n - 身份相关目标、内容和凭据不能为空,完整能力矩阵见命令说明" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified identity/content capability matrix, userId resolution, native local-file upload flow, and fail-closed rejection of unsupported bot/webhook media.", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "dws chat +messages-send --as user --chat-id \u003copenConversationId\u003e --markdown \"## 周报\" --idempotency-key \u003ckey\u003e", + "dws chat +messages-send --as user --user \u003cuserId\u003e --msg-type file --file ./report.pdf" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified identity/content capability matrix, userId resolution, native local-file upload flow, and fail-closed rejection of unsupported bot/webhook media.", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "dws chat +messages-send --as user --chat-id \u003copenConversationId\u003e --markdown \"## 周报\" --idempotency-key \u003ckey\u003e", + "dws chat +messages-send --as user --user \u003cuserId\u003e --msg-type file --file ./report.pdf" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "unknown" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "unknown" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "Reviewed composite send adapter: it selects current-user, bot, or webhook transport; current-user additionally supports userId resolution, mediaId images, and the native init/upload/commit local-file flow." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "Reviewed composite send adapter: it selects current-user, bot, or webhook transport; current-user additionally supports userId resolution, mediaId images, and the native init/upload/commit local-file flow." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified identity/content capability matrix, userId resolution, native local-file upload flow, and fail-closed rejection of unsupported bot/webhook media.", + "selected": false, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "medium" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "medium" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "统一发送文本、Markdown、当前用户文件或已有 mediaId 图片" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "统一发送文本、Markdown、当前用户文件或已有 mediaId 图片" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified identity/content capability matrix, userId resolution, native local-file upload flow, and fail-closed rejection of unsupported bot/webhook media.", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "当你希望用同一个入口选择 current-user、bot 或 webhook 身份发送消息时使用;命令会按身份校验目标、内容和凭据并路由到真实下层。current-user 支持文本/Markdown、已有 mediaId 图片、安全相对路径文件上传和幂等键;bot 支持群聊或批量单聊文本/Markdown;webhook 的目标由 token 所在群决定。不会把 user 文件能力伪装成 bot/webhook 等价能力。" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified identity/content capability matrix, userId resolution, native local-file upload flow, and fail-closed rejection of unsupported bot/webhook media.", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "当你希望用同一个入口选择 current-user、bot 或 webhook 身份发送消息时使用;命令会按身份校验目标、内容和凭据并路由到真实下层。current-user 支持文本/Markdown、已有 mediaId 图片、安全相对路径文件上传和幂等键;bot 支持群聊或批量单聊文本/Markdown;webhook 的目标由 token 所在群决定。不会把 user 文件能力伪装成 bot/webhook 等价能力。" + ] + } + }, + "has_parameters": true, + "idempotency": "unknown", + "interface_mode": "composite", + "interface_reason": "Reviewed composite send adapter: it selects current-user, bot, or webhook transport; current-user additionally supports userId resolution, mediaId images, and the native init/upload/commit local-file flow.", + "is_alias": false, + "name": "shortcut_messages_send", + "parameter_count": 24, + "parameters": { + "ai-tag": { + "default": "true", + "description": "消息是否带 AI 发送角标(默认 true)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "消息是否带 AI 发送角标(默认 true)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "消息是否带 AI 发送角标(默认 true)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "aiTag" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "aiTag" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "property": "aiTag", + "required": false, + "type": "boolean" + }, + "as": { + "description": "--identity 的 lark-cli 对齐别名(可选值: user, bot, webhook)", + "enum": [ + "user", + "bot", + "webhook" + ], + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "--identity 的 lark-cli 对齐别名(可选值: user, bot, webhook)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "--identity 的 lark-cli 对齐别名(可选值: user, bot, webhook)" + }, + "enum": { + "candidates": [ + { + "precedence": "native_annotation", + "selected": true, + "source": "native_annotation", + "value": [ + "user", + "bot", + "webhook" + ] + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": [] + } + ], + "precedence": "native_annotation", + "resolution": "highest_precedence", + "source": "native_annotation", + "value": [ + "user", + "bot", + "webhook" + ] + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "as" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "as" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "as", + "required": false, + "type": "string" + }, + "at-all": { + "description": "@所有人", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "@所有人" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "@所有人" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "atAll" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "atAll" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "property": "atAll", + "required": false, + "type": "boolean" + }, + "at-mobiles": { + "description": "@ 的手机号(webhook)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "@ 的手机号(webhook)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "@ 的手机号(webhook)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "atMobiles" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "atMobiles" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "array" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "array" + } + }, + "property": "atMobiles", + "required": false, + "type": "array" + }, + "at-open-dingtalk-ids": { + "description": "@ 的 openDingTalkId(user/bot 群聊)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "@ 的 openDingTalkId(user/bot 群聊)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "@ 的 openDingTalkId(user/bot 群聊)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "atOpenDingtalkIds" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "atOpenDingtalkIds" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "array" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "array" + } + }, + "property": "atOpenDingtalkIds", + "required": false, + "type": "array" + }, + "at-user-ids": { + "description": "@ 的 userId(bot/webhook)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "@ 的 userId(bot/webhook)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "@ 的 userId(bot/webhook)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "atUserIds" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "atUserIds" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "array" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "array" + } + }, + "property": "atUserIds", + "required": false, + "type": "array" + }, + "chat-id": { + "description": "--group 的 lark-cli 对齐别名", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "--group 的 lark-cli 对齐别名" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "--group 的 lark-cli 对齐别名" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "chatId" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "chatId" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "chatId", + "required": false, + "type": "string" + }, + "file": { + "description": "工作目录内安全相对文件路径(仅 user 的 file/audio/video)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "工作目录内安全相对文件路径(仅 user 的 file/audio/video)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "工作目录内安全相对文件路径(仅 user 的 file/audio/video)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "file" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "file" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "file", + "required": false, + "type": "string" + }, + "file-path": { + "description": "--file 的兼容别名", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "--file 的兼容别名" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "--file 的兼容别名" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "filePath" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "filePath" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "filePath", + "required": false, + "type": "string" + }, + "group": { + "description": "群 openConversationId(user/bot 群聊)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "群 openConversationId(user/bot 群聊)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "群 openConversationId(user/bot 群聊)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "group" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "group" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "group", + "required": false, + "type": "string" + }, + "idempotency-key": { + "description": "--uuid 的 lark-cli 对齐别名(仅 user)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "--uuid 的 lark-cli 对齐别名(仅 user)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "--uuid 的 lark-cli 对齐别名(仅 user)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "idempotencyKey" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "idempotencyKey" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "idempotencyKey", + "required": false, + "type": "string" + }, + "identity": { + "default": "user", + "description": "发送身份;身份相关目标、内容和凭据不能为空,完整能力矩阵见命令说明(可选值: user, bot, webhook)", + "enum": [ + "user", + "bot", + "webhook" + ], + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "发送身份;身份相关目标、内容和凭据不能为空,完整能力矩阵见命令说明(可选值: user, bot, webhook)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "发送身份;身份相关目标、内容和凭据不能为空,完整能力矩阵见命令说明(可选值: user, bot, webhook)" + }, + "enum": { + "candidates": [ + { + "precedence": "native_annotation", + "selected": true, + "source": "native_annotation", + "value": [ + "user", + "bot", + "webhook" + ] + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": [] + } + ], + "precedence": "native_annotation", + "resolution": "highest_precedence", + "source": "native_annotation", + "value": [ + "user", + "bot", + "webhook" + ] + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "identity" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "identity" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "identity", + "required": false, + "type": "string" + }, + "markdown": { + "description": "Markdown 正文", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "Markdown 正文" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "Markdown 正文" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "markdown" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "markdown" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "markdown", + "required": false, + "type": "string" + }, + "media-id": { + "description": "已有图片 mediaId(仅 user 的 image)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "已有图片 mediaId(仅 user 的 image)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "已有图片 mediaId(仅 user 的 image)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "mediaId" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "mediaId" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "mediaId", + "required": false, + "type": "string" + }, + "msg-type": { + "description": "内容类型;省略时根据正文、--media-id 或 --file 自动推断(可选值: text, markdown, image, file, audio, video)", + "enum": [ + "text", + "markdown", + "image", + "file", + "audio", + "video" + ], + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "内容类型;省略时根据正文、--media-id 或 --file 自动推断(可选值: text, markdown, image, file, audio, video)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "内容类型;省略时根据正文、--media-id 或 --file 自动推断(可选值: text, markdown, image, file, audio, video)" + }, + "enum": { + "candidates": [ + { + "precedence": "native_annotation", + "selected": true, + "source": "native_annotation", + "value": [ + "text", + "markdown", + "image", + "file", + "audio", + "video" + ] + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": [] + } + ], + "precedence": "native_annotation", + "resolution": "highest_precedence", + "source": "native_annotation", + "value": [ + "text", + "markdown", + "image", + "file", + "audio", + "video" + ] + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "msgType" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "msgType" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "msgType", + "required": false, + "type": "string" + }, + "open-dingtalk-id": { + "description": "单聊接收者 openDingTalkId(user)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "单聊接收者 openDingTalkId(user)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "单聊接收者 openDingTalkId(user)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "openDingtalkId" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "openDingtalkId" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "openDingtalkId", + "required": false, + "type": "string" + }, + "open-dingtalk-ids": { + "description": "批量单聊接收者 openDingTalkId(bot)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "批量单聊接收者 openDingTalkId(bot)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "批量单聊接收者 openDingTalkId(bot)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "openDingtalkIds" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "openDingtalkIds" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "array" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "array" + } + }, + "property": "openDingtalkIds", + "required": false, + "type": "array" + }, + "robot-code": { + "description": "机器人 Code(identity=bot 时使用)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "机器人 Code(identity=bot 时使用)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "机器人 Code(identity=bot 时使用)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "robotCode" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "robotCode" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "robotCode", + "required": false, + "type": "string" + }, + "text": { + "description": "纯文本正文", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "纯文本正文" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "纯文本正文" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "text" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "text" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "text", + "required": false, + "type": "string" + }, + "title": { + "description": "消息标题(不传则从正文生成)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "消息标题(不传则从正文生成)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "消息标题(不传则从正文生成)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "title" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "title" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "title", + "required": false, + "type": "string" + }, + "user": { + "description": "单聊接收者 userId(user;自动解析 openDingTalkId)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "单聊接收者 userId(user;自动解析 openDingTalkId)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "单聊接收者 userId(user;自动解析 openDingTalkId)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "user" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "user" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "user", + "required": false, + "type": "string" + }, + "users": { + "description": "批量单聊接收者 userId(bot)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "批量单聊接收者 userId(bot)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "批量单聊接收者 userId(bot)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "users" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "users" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "array" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "array" + } + }, + "property": "users", + "required": false, + "type": "array" + }, + "uuid": { + "description": "幂等键(仅 user)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "幂等键(仅 user)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "幂等键(仅 user)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "uuid" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "uuid" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "uuid", + "required": false, + "type": "string" + }, + "webhook-token": { + "description": "自定义机器人 Webhook token(identity=webhook 时使用)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "自定义机器人 Webhook token(identity=webhook 时使用)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "自定义机器人 Webhook token(identity=webhook 时使用)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "webhookToken" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "webhookToken" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "webhookToken", + "required": false, + "type": "string" + } + }, + "path": "chat.shortcut_messages_send", + "primary_cli_path": "chat +messages-send", + "product_id": "chat", + "reviewed": true, + "risk": "medium", + "source": "reviewed_command_registry", + "title": "统一发送文本、Markdown、当前用户文件或已有 mediaId 图片", + "use_when": [ + "当你希望用同一个入口选择 current-user、bot 或 webhook 身份发送消息时使用;命令会按身份校验目标、内容和凭据并路由到真实下层。current-user 支持文本/Markdown、已有 mediaId 图片、安全相对路径文件上传和幂等键;bot 支持群聊或批量单聊文本/Markdown;webhook 的目标由 token 所在群决定。不会把 user 文件能力伪装成 bot/webhook 等价能力。" + ] + }, "chat.shortcut_messages_send_by_webhook": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ @@ -71556,6 +75592,697 @@ "当你只有自定义机器人的 Webhook token、想往其所在群推送消息时使用;会实际通过 Webhook 发群消息,需传 token、标题、正文,可 @手机号/userId 或 @所有人。" ] }, + "chat.shortcut_messages_send_card": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "ShortcutRegistry:chat +messages-send-card", + "cobra-help:dws chat +messages-send-card", + "internal/cli/schema_command_registry.json#chat.shortcut_messages_send_card", + "internal/cli/schema_hints/metadata/chat.json", + "internal/cli/schema_hints/selection/chat.json" + ], + "agent_summary": "创建流式卡片,可在同一次调用中写入内容并结束", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "已有 bizId、只需要追加或更新现有卡片内容时使用 +messages-update-card" + ], + "canonical_path": "chat.shortcut_messages_send_card", + "cli_name": "+messages-send-card", + "cli_path": "chat +messages-send-card", + "confirmation": "user_required", + "constraints": { + "mutually_exclusive": [ + [ + "group", + "receiver" + ] + ], + "require_one_of": [ + [ + "group", + "receiver" + ] + ] + }, + "description": "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId 二选一。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。\n\n参数约束:\n - --group、--receiver 必须且只能指定一个\n - --flow-status 必须在 1-5 之间,且显式指定时必须同时提供 --content", + "display": "群聊 / 消息 / 机器人", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws chat +messages-send-card --group \u003copenConversationId\u003e --content \"任务已完成\"", + "dws chat +messages-send-card --receiver \u003cuserId\u003e" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, two-action dry-run, bizId extraction, and partial-failure reporting.", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": "创建流式卡片,可在同一次调用中写入内容并结束" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, two-action dry-run, bizId extraction, and partial-failure reporting.", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": "创建流式卡片,可在同一次调用中写入内容并结束" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, two-action dry-run, bizId extraction, and partial-failure reporting.", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "已有 bizId、只需要追加或更新现有卡片内容时使用 +messages-update-card" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, two-action dry-run, bizId extraction, and partial-failure reporting.", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "已有 bizId、只需要追加或更新现有卡片内容时使用 +messages-update-card" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "chat +messages-send-card", + "value": "chat.shortcut_messages_send_card" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "chat +messages-send-card", + "value": "chat.shortcut_messages_send_card" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "user_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "user_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId 二选一。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。\n\n参数约束:\n - --group、--receiver 必须且只能指定一个\n - --flow-status 必须在 1-5 之间,且显式指定时必须同时提供 --content" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId 二选一。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。\n\n参数约束:\n - --group、--receiver 必须且只能指定一个\n - --flow-status 必须在 1-5 之间,且显式指定时必须同时提供 --content" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, two-action dry-run, bizId extraction, and partial-failure reporting.", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "dws chat +messages-send-card --group \u003copenConversationId\u003e --content \"任务已完成\"", + "dws chat +messages-send-card --receiver \u003cuserId\u003e" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, two-action dry-run, bizId extraction, and partial-failure reporting.", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "dws chat +messages-send-card --group \u003copenConversationId\u003e --content \"任务已完成\"", + "dws chat +messages-send-card --receiver \u003cuserId\u003e" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "unknown" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "unknown" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "Reviewed card lifecycle adapter: it can call create_and_send_card alone or compose it with update_streaming_card after extracting the returned bizId." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "Reviewed card lifecycle adapter: it can call create_and_send_card alone or compose it with update_streaming_card after extracting the returned bizId." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, two-action dry-run, bizId extraction, and partial-failure reporting.", + "selected": false, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "medium" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "medium" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "创建流式卡片,可在同一次调用中写入内容并结束" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "创建流式卡片,可在同一次调用中写入内容并结束" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, two-action dry-run, bizId extraction, and partial-failure reporting.", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId 二选一。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, two-action dry-run, bizId extraction, and partial-failure reporting.", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId 二选一。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。" + ] + } + }, + "has_parameters": true, + "idempotency": "unknown", + "interface_mode": "composite", + "interface_reason": "Reviewed card lifecycle adapter: it can call create_and_send_card alone or compose it with update_streaming_card after extracting the returned bizId.", + "is_alias": false, + "name": "shortcut_messages_send_card", + "parameter_count": 4, + "parameters": { + "content": { + "description": "创建后立即写入的卡片内容;省略时仅创建并返回 bizId", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "创建后立即写入的卡片内容;省略时仅创建并返回 bizId" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "创建后立即写入的卡片内容;省略时仅创建并返回 bizId" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "content" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "content" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "content", + "required": false, + "type": "string" + }, + "flow-status": { + "default": "3", + "description": "自动更新状态:1处理中/2输入中/3完成/4执行中/5错误;--flow-status 必须在 1-5 之间,且显式指定时必须同时提供 --content", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "自动更新状态:1处理中/2输入中/3完成/4执行中/5错误;--flow-status 必须在 1-5 之间,且显式指定时必须同时提供 --content" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "自动更新状态:1处理中/2输入中/3完成/4执行中/5错误;--flow-status 必须在 1-5 之间,且显式指定时必须同时提供 --content" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "flowStatus" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "flowStatus" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "integer" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "integer" + } + }, + "property": "flowStatus", + "required": false, + "type": "integer" + }, + "group": { + "description": "群 openConversationId(与 --receiver 互斥)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "群 openConversationId(与 --receiver 互斥)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "群 openConversationId(与 --receiver 互斥)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "group" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "group" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "group", + "required": false, + "type": "string" + }, + "receiver": { + "description": "单聊接收者 userId(与 --group 互斥)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "单聊接收者 userId(与 --group 互斥)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "单聊接收者 userId(与 --group 互斥)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "receiver" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "receiver" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "receiver", + "required": false, + "type": "string" + } + }, + "path": "chat.shortcut_messages_send_card", + "primary_cli_path": "chat +messages-send-card", + "product_id": "chat", + "reviewed": true, + "risk": "medium", + "source": "reviewed_command_registry", + "title": "创建流式卡片,可在同一次调用中写入内容并结束", + "use_when": [ + "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId 二选一。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。" + ] + }, "chat.shortcut_messages_update_card": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ @@ -72744,6 +77471,2808 @@ "当你想快速看一眼自己都加入了哪些群、以及每个群的会话ID、名称、群主和人数,而不想翻分页或盯着原始返回时使用;内部分页拉取你加入的群列表,把每个群防御式地投影成 会话id / 名称 / 群主 / 人数 / 类型 等关键字段,输出成干净的结果。可选 --type 在本地按群类型过滤(底层接口本身不带类型参数,故为客户端过滤)。这是只读操作,不会改动任何群或成员关系。" ] }, + "chat.shortcut_search_msg": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "ShortcutRegistry:chat +search-msg", + "cobra-help:dws chat +search-msg", + "internal/cli/schema_command_registry.json#chat.shortcut_search_msg", + "internal/cli/schema_hints/metadata/chat.json", + "internal/cli/schema_hints/selection/chat.json" + ], + "agent_summary": "多维搜索消息,可全量翻页并批量富化详情", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "只想读取一个已知会话的连续历史时使用 +chat-messages;已有精确消息 ID 时使用 +messages-mget" + ], + "canonical_path": "chat.shortcut_search_msg", + "cli_name": "+search-msg", + "cli_path": "chat +search-msg", + "confirmation": "not_required", + "constraints": { + "mutually_exclusive": [ + [ + "groups", + "chat-id" + ], + [ + "senders", + "sender" + ], + [ + "at-me", + "is-at-me" + ], + [ + "conversation-type", + "chat-type" + ], + [ + "limit", + "page-size" + ], + [ + "cursor", + "page-token" + ] + ], + "require_one_of": [ + [ + "query", + "group", + "groups", + "chat-id", + "senders", + "sender", + "at-me", + "is-at-me", + "at-ids", + "message-type", + "only-robot", + "conversation-type", + "chat-type" + ] + ] + }, + "description": "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。\n\n参数约束:\n - 至少指定一个内容、身份、会话或消息类型过滤条件\n - 需与 --end 一起传\n - 需与 --start 一起传\n - --groups、--chat-id 互斥,最多指定一个\n - --senders、--sender 互斥,最多指定一个\n - --at-me、--is-at-me 互斥,最多指定一个\n - --conversation-type、--chat-type 互斥,最多指定一个\n - --limit、--page-size 互斥,最多指定一个\n - --cursor、--page-token 互斥,最多指定一个\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", + "display": "群聊 / 消息 / 机器人", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws chat +search-msg --query \"周报\" --senders \u003copenDingTalkId\u003e --days 3 --page-all", + "dws chat +search-msg --group \u003copenConversationId\u003e --message-type file --download-resources --output-dir ./downloads" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified filter mapping, cursor pagination, batched mget enrichment, completeness ledger, and shared safe resource workflow.", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": "多维搜索消息,可全量翻页并批量富化详情" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified filter mapping, cursor pagination, batched mget enrichment, completeness ledger, and shared safe resource workflow.", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": "多维搜索消息,可全量翻页并批量富化详情" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified filter mapping, cursor pagination, batched mget enrichment, completeness ledger, and shared safe resource workflow.", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "只想读取一个已知会话的连续历史时使用 +chat-messages;已有精确消息 ID 时使用 +messages-mget" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified filter mapping, cursor pagination, batched mget enrichment, completeness ledger, and shared safe resource workflow.", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "只想读取一个已知会话的连续历史时使用 +chat-messages;已有精确消息 ID 时使用 +messages-mget" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "chat +search-msg", + "value": "chat.shortcut_search_msg" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "chat +search-msg", + "value": "chat.shortcut_search_msg" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。\n\n参数约束:\n - 至少指定一个内容、身份、会话或消息类型过滤条件\n - 需与 --end 一起传\n - 需与 --start 一起传\n - --groups、--chat-id 互斥,最多指定一个\n - --senders、--sender 互斥,最多指定一个\n - --at-me、--is-at-me 互斥,最多指定一个\n - --conversation-type、--chat-type 互斥,最多指定一个\n - --limit、--page-size 互斥,最多指定一个\n - --cursor、--page-token 互斥,最多指定一个\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。\n\n参数约束:\n - 至少指定一个内容、身份、会话或消息类型过滤条件\n - 需与 --end 一起传\n - 需与 --start 一起传\n - --groups、--chat-id 互斥,最多指定一个\n - --senders、--sender 互斥,最多指定一个\n - --at-me、--is-at-me 互斥,最多指定一个\n - --conversation-type、--chat-type 互斥,最多指定一个\n - --limit、--page-size 互斥,最多指定一个\n - --cursor、--page-token 互斥,最多指定一个\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "read" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "read" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified filter mapping, cursor pagination, batched mget enrichment, completeness ledger, and shared safe resource workflow.", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "dws chat +search-msg --query \"周报\" --senders \u003copenDingTalkId\u003e --days 3 --page-all", + "dws chat +search-msg --group \u003copenConversationId\u003e --message-type file --download-resources --output-dir ./downloads" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified filter mapping, cursor pagination, batched mget enrichment, completeness ledger, and shared safe resource workflow.", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "dws chat +search-msg --query \"周报\" --senders \u003copenDingTalkId\u003e --days 3 --page-all", + "dws chat +search-msg --group \u003copenConversationId\u003e --message-type file --download-resources --output-dir ./downloads" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "Reviewed search adapter: it combines filters, cursor pagination, batched mget enrichment, stable projection, completeness accounting, and optional safe resource downloads." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "Reviewed search adapter: it combines filters, cursor pagination, batched mget enrichment, stable projection, completeness accounting, and optional safe resource downloads." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified filter mapping, cursor pagination, batched mget enrichment, completeness ledger, and shared safe resource workflow.", + "selected": false, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "多维搜索消息,可全量翻页并批量富化详情" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "多维搜索消息,可全量翻页并批量富化详情" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified filter mapping, cursor pagination, batched mget enrichment, completeness ledger, and shared safe resource workflow.", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified filter mapping, cursor pagination, batched mget enrichment, completeness ledger, and shared safe resource workflow.", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。" + ] + } + }, + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed search adapter: it combines filters, cursor pagination, batched mget enrichment, stable projection, completeness accounting, and optional safe resource downloads.", + "is_alias": false, + "name": "shortcut_search_msg", + "parameter_count": 27, + "parameters": { + "at-ids": { + "description": "@对象 userId/openDingTalkId 列表", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "@对象 userId/openDingTalkId 列表" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "@对象 userId/openDingTalkId 列表" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "atIds" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "atIds" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "array" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "array" + } + }, + "property": "atIds", + "required": false, + "type": "array" + }, + "at-me": { + "description": "只搜索 @我 的消息", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "只搜索 @我 的消息" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "只搜索 @我 的消息" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "atMe" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "atMe" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "property": "atMe", + "required": false, + "type": "boolean" + }, + "chat-id": { + "description": "--groups 的 lark-cli 对齐别名", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "--groups 的 lark-cli 对齐别名" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "--groups 的 lark-cli 对齐别名" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "chatId" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "chatId" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "array" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "array" + } + }, + "property": "chatId", + "required": false, + "type": "array" + }, + "chat-type": { + "description": "--conversation-type 的 lark-cli 对齐别名", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "--conversation-type 的 lark-cli 对齐别名" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "--conversation-type 的 lark-cli 对齐别名" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "chatType" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "chatType" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "chatType", + "required": false, + "type": "string" + }, + "conversation-type": { + "description": "下层会话类型过滤值(以当前 IM Schema 为准)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "下层会话类型过滤值(以当前 IM Schema 为准)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "下层会话类型过滤值(以当前 IM Schema 为准)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "conversationType" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "conversationType" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "conversationType", + "required": false, + "type": "string" + }, + "cursor": { + "default": "0", + "description": "分页游标,翻页传上次的 nextCursor", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "分页游标,翻页传上次的 nextCursor" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "分页游标,翻页传上次的 nextCursor" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "cursor" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "cursor" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "cursor", + "required": false, + "type": "string" + }, + "days": { + "default": "7", + "description": "默认时间窗的回溯天数", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "默认时间窗的回溯天数" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "默认时间窗的回溯天数" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "days" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "days" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "integer" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "integer" + } + }, + "property": "days", + "required": false, + "type": "integer" + }, + "download-resources": { + "description": "自动下载消息中的全部可识别 mediaId/fileId 资源", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "自动下载消息中的全部可识别 mediaId/fileId 资源" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "自动下载消息中的全部可识别 mediaId/fileId 资源" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "downloadResources" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "downloadResources" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "property": "downloadResources", + "required": false, + "type": "boolean" + }, + "end": { + "description": "精确结束时间(RFC3339,需与 --start 一起传)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "精确结束时间(RFC3339,需与 --start 一起传)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "精确结束时间(RFC3339,需与 --start 一起传)" + }, + "format": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_format_inference", + "value": "date-time" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_format_inference", + "value": "date-time" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "end" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "end" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "format": "date-time", + "property": "end", + "required": false, + "type": "string" + }, + "group": { + "description": "单个会话 openConversationId", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "单个会话 openConversationId" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "单个会话 openConversationId" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "group" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "group" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "group", + "required": false, + "type": "string" + }, + "groups": { + "description": "多个会话 openConversationId", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "多个会话 openConversationId" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "多个会话 openConversationId" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "groups" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "groups" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "array" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "array" + } + }, + "property": "groups", + "required": false, + "type": "array" + }, + "is-at-me": { + "description": "--at-me 的 lark-cli 对齐别名", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "--at-me 的 lark-cli 对齐别名" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "--at-me 的 lark-cli 对齐别名" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "isAtMe" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "isAtMe" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "property": "isAtMe", + "required": false, + "type": "boolean" + }, + "limit": { + "default": "100", + "description": "每页返回数量(1-100)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "每页返回数量(1-100)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "每页返回数量(1-100)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "limit" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "limit" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "integer" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "integer" + } + }, + "property": "limit", + "required": false, + "type": "integer" + }, + "message-type": { + "description": "下层消息类型过滤值(以当前 IM Schema 为准)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "下层消息类型过滤值(以当前 IM Schema 为准)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "下层消息类型过滤值(以当前 IM Schema 为准)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "messageType" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "messageType" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "messageType", + "required": false, + "type": "string" + }, + "no-enrich": { + "description": "不再按消息 ID 批量查询完整详情", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "不再按消息 ID 批量查询完整详情" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "不再按消息 ID 批量查询完整详情" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "noEnrich" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "noEnrich" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "property": "noEnrich", + "required": false, + "type": "boolean" + }, + "no-reactions": { + "description": "不输出命中消息的 reaction(默认输出)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "不输出命中消息的 reaction(默认输出)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "不输出命中消息的 reaction(默认输出)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "noReactions" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "noReactions" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "property": "noReactions", + "required": false, + "type": "boolean" + }, + "only-robot": { + "description": "只搜索机器人消息", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "只搜索机器人消息" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "只搜索机器人消息" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "onlyRobot" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "onlyRobot" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "property": "onlyRobot", + "required": false, + "type": "boolean" + }, + "output-dir": { + "default": "./downloads", + "description": "资源输出目录;必须是工作目录内的相对路径,禁止绝对路径和 .. 逃逸", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "资源输出目录;必须是工作目录内的相对路径,禁止绝对路径和 .. 逃逸" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "资源输出目录;必须是工作目录内的相对路径,禁止绝对路径和 .. 逃逸" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "outputDir" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "outputDir" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "outputDir", + "required": false, + "type": "string" + }, + "overwrite": { + "description": "允许覆盖同名资源文件(默认拒绝)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "允许覆盖同名资源文件(默认拒绝)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "允许覆盖同名资源文件(默认拒绝)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "overwrite" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "overwrite" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "property": "overwrite", + "required": false, + "type": "boolean" + }, + "page-all": { + "description": "自动连续拉取所有游标页", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "自动连续拉取所有游标页" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "自动连续拉取所有游标页" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "pageAll" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "pageAll" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "property": "pageAll", + "required": false, + "type": "boolean" + }, + "page-limit": { + "default": "20", + "description": "--page-all 的最大页数(1-40)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "--page-all 的最大页数(1-40)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "--page-all 的最大页数(1-40)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "pageLimit" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "pageLimit" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "integer" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "integer" + } + }, + "property": "pageLimit", + "required": false, + "type": "integer" + }, + "page-size": { + "description": "--limit 的 lark-cli 对齐别名(1-100)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "--limit 的 lark-cli 对齐别名(1-100)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "--limit 的 lark-cli 对齐别名(1-100)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "pageSize" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "pageSize" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "integer" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "integer" + } + }, + "property": "pageSize", + "required": false, + "type": "integer" + }, + "page-token": { + "description": "--cursor 的 lark-cli 对齐别名", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "--cursor 的 lark-cli 对齐别名" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "--cursor 的 lark-cli 对齐别名" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "pageToken" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "pageToken" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "pageToken", + "required": false, + "type": "string" + }, + "query": { + "description": "搜索关键词", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "搜索关键词" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "搜索关键词" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "query" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "query" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "query", + "required": false, + "type": "string" + }, + "sender": { + "description": "--senders 的 lark-cli 对齐别名", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "--senders 的 lark-cli 对齐别名" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "--senders 的 lark-cli 对齐别名" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "sender" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "sender" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "array" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "array" + } + }, + "property": "sender", + "required": false, + "type": "array" + }, + "senders": { + "description": "发送者 userId/openDingTalkId 列表", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "发送者 userId/openDingTalkId 列表" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "发送者 userId/openDingTalkId 列表" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "senders" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "senders" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "array" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "array" + } + }, + "property": "senders", + "required": false, + "type": "array" + }, + "start": { + "description": "精确开始时间(RFC3339,需与 --end 一起传)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "精确开始时间(RFC3339,需与 --end 一起传)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "精确开始时间(RFC3339,需与 --end 一起传)" + }, + "format": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_format_inference", + "value": "date-time" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_format_inference", + "value": "date-time" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "start" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "start" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "format": "date-time", + "property": "start", + "required": false, + "type": "string" + } + }, + "path": "chat.shortcut_search_msg", + "primary_cli_path": "chat +search-msg", + "product_id": "chat", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "多维搜索消息,可全量翻页并批量富化详情", + "use_when": [ + "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。" + ] + }, "chat.shortcut_send_to_group": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ @@ -73346,6 +80875,1141 @@ "当你只知道群的名字、想直接往这个群里发一条消息而不想先手动查群 ID 时使用;内部先按群名搜索群聊解析出唯一 openConversationId 再发送,群名匹配到多个群时会列出候选让你区分、绝不自行假定。会真实发出群消息。" ] }, + "chat.shortcut_thread_replies": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "ShortcutRegistry:chat +thread-replies", + "cobra-help:dws chat +thread-replies", + "internal/cli/schema_command_registry.json#chat.shortcut_thread_replies", + "internal/cli/schema_hints/metadata/chat.json", + "internal/cli/schema_hints/selection/chat.json" + ], + "agent_summary": "拉取某条话题消息的全部回复并投影出发言人/文本/时间", + "agent_summary_source": "dws-agent-selection/chat", + "availability": "available", + "avoid_when": [ + "要回复 Thread 或发送新回复时不要使用此只读入口;当前没有经过验证的 thread writer Shortcut" + ], + "canonical_path": "chat.shortcut_thread_replies", + "cli_name": "+thread-replies", + "cli_path": "chat +thread-replies", + "confirmation": "not_required", + "constraints": { + "mutually_exclusive": [ + [ + "thread-id", + "topic-id" + ] + ], + "require_one_of": [ + [ + "thread-id", + "topic-id" + ] + ] + }, + "description": "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。\n\n参数约束:\n - --thread-id 与兼容参数 --topic-id 必须且只能指定一个\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", + "display": "群聊 / 消息 / 机器人", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws chat +thread-replies --group \u003copenConversationId\u003e --thread-id \u003cthreadId\u003e", + "dws chat +thread-replies --group \u003copenConversationId\u003e --topic-id \u003ctopicId\u003e --download-resources --output-dir ./downloads" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified thread/topic alias, lower list_topic_replies mapping, stable projection, group-context fallback, and optional safe resource workflow.", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": "拉取某条话题消息的全部回复并投影出发言人/文本/时间" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified thread/topic alias, lower list_topic_replies mapping, stable projection, group-context fallback, and optional safe resource workflow.", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": "拉取某条话题消息的全部回复并投影出发言人/文本/时间" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified thread/topic alias, lower list_topic_replies mapping, stable projection, group-context fallback, and optional safe resource workflow.", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "要回复 Thread 或发送新回复时不要使用此只读入口;当前没有经过验证的 thread writer Shortcut" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified thread/topic alias, lower list_topic_replies mapping, stable projection, group-context fallback, and optional safe resource workflow.", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "要回复 Thread 或发送新回复时不要使用此只读入口;当前没有经过验证的 thread writer Shortcut" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "chat +thread-replies", + "value": "chat.shortcut_thread_replies" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "chat +thread-replies", + "value": "chat.shortcut_thread_replies" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。\n\n参数约束:\n - --thread-id 与兼容参数 --topic-id 必须且只能指定一个\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。\n\n参数约束:\n - --thread-id 与兼容参数 --topic-id 必须且只能指定一个\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "read" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "read" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified thread/topic alias, lower list_topic_replies mapping, stable projection, group-context fallback, and optional safe resource workflow.", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "dws chat +thread-replies --group \u003copenConversationId\u003e --thread-id \u003cthreadId\u003e", + "dws chat +thread-replies --group \u003copenConversationId\u003e --topic-id \u003ctopicId\u003e --download-resources --output-dir ./downloads" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified thread/topic alias, lower list_topic_replies mapping, stable projection, group-context fallback, and optional safe resource workflow.", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "dws chat +thread-replies --group \u003copenConversationId\u003e --thread-id \u003cthreadId\u003e", + "dws chat +thread-replies --group \u003copenConversationId\u003e --topic-id \u003ctopicId\u003e --download-resources --output-dir ./downloads" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "idempotent" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "Reviewed thread reader adapter: it accepts threadId/topicId, reads lower topic replies, projects stable message fields, and optionally downloads reply resources safely." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "Reviewed thread reader adapter: it accepts threadId/topicId, reads lower topic replies, projects stable message fields, and optionally downloads reply resources safely." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified thread/topic alias, lower list_topic_replies mapping, stable projection, group-context fallback, and optional safe resource workflow.", + "selected": false, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "low" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "source": "internal/cli/schema_hints/metadata/chat.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "拉取某条话题消息的全部回复并投影出发言人/文本/时间" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "拉取某条话题消息的全部回复并投影出发言人/文本/时间" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Agent-authored from the verified thread/topic alias, lower list_topic_replies mapping, stable projection, group-context fallback, and optional safe resource workflow.", + "selected": true, + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Agent-authored from the verified thread/topic alias, lower list_topic_replies mapping, stable projection, group-context fallback, and optional safe resource workflow.", + "source": "internal/cli/schema_hints/selection/chat.json", + "value": [ + "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + ] + } + }, + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed thread reader adapter: it accepts threadId/topicId, reads lower topic replies, projects stable message fields, and optionally downloads reply resources safely.", + "is_alias": false, + "name": "shortcut_thread_replies", + "parameter_count": 9, + "parameters": { + "download-resources": { + "description": "自动下载消息中的全部可识别 mediaId/fileId 资源", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "自动下载消息中的全部可识别 mediaId/fileId 资源" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "自动下载消息中的全部可识别 mediaId/fileId 资源" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "downloadResources" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "downloadResources" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "property": "downloadResources", + "required": false, + "type": "boolean" + }, + "group": { + "description": "群会话 ID(openConversationId,必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "群会话 ID(openConversationId,必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "群会话 ID(openConversationId,必填)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "group" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "group" + }, + "required": { + "candidates": [ + { + "precedence": "native_annotation", + "selected": true, + "source": "native_annotation", + "value": true + }, + { + "precedence": "inference", + "selected": false, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "native_annotation", + "resolution": "highest_precedence", + "source": "native_annotation", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "group", + "required": true, + "type": "string" + }, + "limit": { + "description": "每页拉取的回复条数(可选)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "每页拉取的回复条数(可选)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "每页拉取的回复条数(可选)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "limit" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "limit" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "integer" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "integer" + } + }, + "property": "limit", + "required": false, + "type": "integer" + }, + "no-reactions": { + "description": "不输出回复 reaction(默认输出)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "不输出回复 reaction(默认输出)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "不输出回复 reaction(默认输出)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "noReactions" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "noReactions" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "property": "noReactions", + "required": false, + "type": "boolean" + }, + "output-dir": { + "default": "./downloads", + "description": "资源输出目录;必须是工作目录内的相对路径,禁止绝对路径和 .. 逃逸", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "资源输出目录;必须是工作目录内的相对路径,禁止绝对路径和 .. 逃逸" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "资源输出目录;必须是工作目录内的相对路径,禁止绝对路径和 .. 逃逸" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "outputDir" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "outputDir" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "outputDir", + "required": false, + "type": "string" + }, + "overwrite": { + "description": "允许覆盖同名资源文件(默认拒绝)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "允许覆盖同名资源文件(默认拒绝)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "允许覆盖同名资源文件(默认拒绝)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "overwrite" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "overwrite" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "boolean" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "boolean" + } + }, + "property": "overwrite", + "required": false, + "type": "boolean" + }, + "thread-id": { + "description": "话题/线程 ID(可直接使用消息列表返回的 threadId)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "话题/线程 ID(可直接使用消息列表返回的 threadId)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "话题/线程 ID(可直接使用消息列表返回的 threadId)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "threadId" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "threadId" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "threadId", + "required": false, + "type": "string" + }, + "time": { + "description": "起始时间,如 \"2025-03-01 00:00:00\"(可选)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "起始时间,如 \"2025-03-01 00:00:00\"(可选)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "起始时间,如 \"2025-03-01 00:00:00\"(可选)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "time" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "time" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "time", + "required": false, + "type": "string" + }, + "topic-id": { + "description": "--thread-id 的兼容别名", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "--thread-id 的兼容别名" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "--thread-id 的兼容别名" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "topicId" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "topicId" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "topicId", + "required": false, + "type": "string" + } + }, + "path": "chat.shortcut_thread_replies", + "primary_cli_path": "chat +thread-replies", + "product_id": "chat", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "拉取某条话题消息的全部回复并投影出发言人/文本/时间", + "use_when": [ + "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + ] + }, "chat.shortcut_unread_chats": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ diff --git a/internal/cli/schema_command_exclusions.json b/internal/cli/schema_command_exclusions.json index f3f349c1..50b8707b 100644 --- a/internal/cli/schema_command_exclusions.json +++ b/internal/cli/schema_command_exclusions.json @@ -168,7 +168,6 @@ "chat +chat-get-by-id", "chat +chat-members-get", "chat +chat-members-list", - "chat +chat-messages", "chat +chat-mute-member", "chat +chat-quit", "chat +chat-remove-bot", @@ -205,15 +204,11 @@ "chat +messages-reply", "chat +messages-resource-download", "chat +messages-resource-url", - "chat +messages-send", "chat +messages-send-by-bot", - "chat +messages-send-card", "chat +messages-set-pin", "chat +messages-set-top", "chat +messages-unset-pin", - "chat +messages-unset-top", - "chat +search-msg", - "chat +thread-replies" + "chat +messages-unset-top" ] } ] diff --git a/internal/cli/schema_command_registry/products/chat.json b/internal/cli/schema_command_registry/products/chat.json index fbb620f4..18cbbec5 100644 --- a/internal/cli/schema_command_registry/products/chat.json +++ b/internal/cli/schema_command_registry/products/chat.json @@ -421,10 +421,22 @@ "canonical_path": "chat.shortcut_chat_role_query_user", "cli_path": "chat +chat-role-query-user" }, + { + "canonical_path": "chat.shortcut_chat_messages", + "cli_path": "chat +chat-messages" + }, + { + "canonical_path": "chat.shortcut_messages_send", + "cli_path": "chat +messages-send" + }, { "canonical_path": "chat.shortcut_messages_send_by_webhook", "cli_path": "chat +messages-send-by-webhook" }, + { + "canonical_path": "chat.shortcut_messages_send_card", + "cli_path": "chat +messages-send-card" + }, { "canonical_path": "chat.shortcut_messages_list_direct", "cli_path": "chat +messages-list-direct" @@ -473,10 +485,18 @@ "canonical_path": "chat.shortcut_my_groups", "cli_path": "chat +my-groups" }, + { + "canonical_path": "chat.shortcut_search_msg", + "cli_path": "chat +search-msg" + }, { "canonical_path": "chat.shortcut_send_to_group", "cli_path": "chat +send-to-group" }, + { + "canonical_path": "chat.shortcut_thread_replies", + "cli_path": "chat +thread-replies" + }, { "canonical_path": "chat.shortcut_unread_chats", "cli_path": "chat +unread-chats" diff --git a/internal/cli/schema_hints/metadata/chat.json b/internal/cli/schema_hints/metadata/chat.json index cd937849..6d11c8bc 100644 --- a/internal/cli/schema_hints/metadata/chat.json +++ b/internal/cli/schema_hints/metadata/chat.json @@ -1152,6 +1152,32 @@ "cli_path": "chat +chat-role-query-user", "runtime_gate": "none" }, + "chat.shortcut_chat_messages": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed built-in Shortcut adapter: it routes group or direct-message history reads, projects a stable message shape, and optionally orchestrates safe resource downloads with a failure ledger.", + "reviewed": true, + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, shared safe-output validation, resource de-duplication, and per-resource failure ledger.", + "cli_path": "chat +chat-messages", + "runtime_gate": "none" + }, + "chat.shortcut_messages_send": { + "effect": "write", + "risk": "medium", + "confirmation": "user_required", + "idempotency": "unknown", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed composite send adapter: it selects current-user, bot, or webhook transport; current-user additionally supports userId resolution, mediaId images, and the native init/upload/commit local-file flow.", + "reviewed": true, + "review_reason": "Reviewed against every executable identity/content/target validation branch and the real lower tool mappings; bot and webhook media are intentionally rejected because no equivalent lower capability exists.", + "cli_path": "chat +messages-send", + "runtime_gate": "typed_yes" + }, "chat.shortcut_messages_send_by_webhook": { "effect": "write", "risk": "medium", @@ -1165,6 +1191,19 @@ "cli_path": "chat +messages-send-by-webhook", "runtime_gate": "typed_yes" }, + "chat.shortcut_messages_send_card": { + "effect": "write", + "risk": "medium", + "confirmation": "user_required", + "idempotency": "unknown", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed card lifecycle adapter: it can call create_and_send_card alone or compose it with update_streaming_card after extracting the returned bizId.", + "reviewed": true, + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId.", + "cli_path": "chat +messages-send-card", + "runtime_gate": "typed_yes" + }, "chat.shortcut_messages_list_direct": { "effect": "read", "risk": "low", @@ -1321,6 +1360,19 @@ "cli_path": "chat +my-groups", "runtime_gate": "none" }, + "chat.shortcut_search_msg": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed search adapter: it combines filters, cursor pagination, batched mget enrichment, stable projection, completeness accounting, and optional safe resource downloads.", + "reviewed": true, + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, shared safe-output validation, and optional per-resource download ledger.", + "cli_path": "chat +search-msg", + "runtime_gate": "none" + }, "chat.shortcut_send_to_group": { "effect": "write", "risk": "medium", @@ -1334,6 +1386,19 @@ "cli_path": "chat +send-to-group", "runtime_gate": "typed_yes" }, + "chat.shortcut_thread_replies": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed thread reader adapter: it accepts threadId/topicId, reads lower topic replies, projects stable message fields, and optionally downloads reply resources safely.", + "reviewed": true, + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, group fallback for resource context, and per-resource failure ledger.", + "cli_path": "chat +thread-replies", + "runtime_gate": "none" + }, "chat.shortcut_unread_chats": { "effect": "read", "risk": "low", diff --git a/internal/cli/schema_hints/runtime-surface-completeness.json b/internal/cli/schema_hints/runtime-surface-completeness.json index 4147a3eb..fc13201c 100644 --- a/internal/cli/schema_hints/runtime-surface-completeness.json +++ b/internal/cli/schema_hints/runtime-surface-completeness.json @@ -7,7 +7,7 @@ "channel": "open-source" }, "coverage": { - "source_tools": 813, + "source_tools": 818, "matched_tools": 71 }, "tools": { diff --git a/internal/cli/schema_hints/selection/chat.json b/internal/cli/schema_hints/selection/chat.json index b54c5735..24066d7f 100644 --- a/internal/cli/schema_hints/selection/chat.json +++ b/internal/cli/schema_hints/selection/chat.json @@ -2110,6 +2110,46 @@ "ShortcutRegistry:chat +chat-role-query-user" ] }, + "chat.shortcut_chat_messages": { + "agent_summary": "拉取某个会话(群聊或单聊)的消息列表并投影出发言人/文本/时间", + "use_when": [ + "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + ], + "avoid_when": [ + "要跨多个会话按关键词、发送者或消息类型检索时使用 +search-msg;已有一批精确消息 ID 时使用 +messages-mget" + ], + "examples": [ + "dws chat +chat-messages --group --direction older", + "dws chat +chat-messages --open-dingtalk-id --download-resources --output-dir ./downloads" + ], + "reviewed": true, + "review_reason": "Agent-authored from the verified group/direct routing, stable projection, pagination, and optional safe resource workflow; it distinguishes conversation history from cross-conversation search and exact-ID mget.", + "source_refs": [ + "internal/cli/schema_command_registry.json#chat.shortcut_chat_messages", + "cobra-help:dws chat +chat-messages", + "ShortcutRegistry:chat +chat-messages" + ] + }, + "chat.shortcut_messages_send": { + "agent_summary": "统一发送文本、Markdown、当前用户文件或已有 mediaId 图片", + "use_when": [ + "当你希望用同一个入口选择 current-user、bot 或 webhook 身份发送消息时使用;命令会按身份校验目标、内容和凭据并路由到真实下层。current-user 支持文本/Markdown、已有 mediaId 图片、安全相对路径文件上传和幂等键;bot 支持群聊或批量单聊文本/Markdown;webhook 的目标由 token 所在群决定。不会把 user 文件能力伪装成 bot/webhook 等价能力。" + ], + "avoid_when": [ + "需要 bot/webhook 发送媒体、卡片或 thread 回复时不要假设等价支持;改用真实存在的专用下层命令,缺少下层能力时停止" + ], + "examples": [ + "dws chat +messages-send --as user --chat-id --markdown \"## 周报\" --idempotency-key ", + "dws chat +messages-send --as user --user --msg-type file --file ./report.pdf" + ], + "reviewed": true, + "review_reason": "Agent-authored from the verified identity/content capability matrix, userId resolution, native local-file upload flow, and fail-closed rejection of unsupported bot/webhook media.", + "source_refs": [ + "internal/cli/schema_command_registry.json#chat.shortcut_messages_send", + "cobra-help:dws chat +messages-send", + "ShortcutRegistry:chat +messages-send" + ] + }, "chat.shortcut_messages_send_by_webhook": { "agent_summary": "自定义机器人 Webhook 发送群消息", "use_when": [ @@ -2129,6 +2169,26 @@ "ShortcutRegistry:chat +messages-send-by-webhook" ] }, + "chat.shortcut_messages_send_card": { + "agent_summary": "创建流式卡片,可在同一次调用中写入内容并结束", + "use_when": [ + "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId 二选一。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。" + ], + "avoid_when": [ + "已有 bizId、只需要追加或更新现有卡片内容时使用 +messages-update-card" + ], + "examples": [ + "dws chat +messages-send-card --group --content \"任务已完成\"", + "dws chat +messages-send-card --receiver " + ], + "reviewed": true, + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, two-action dry-run, bizId extraction, and partial-failure reporting.", + "source_refs": [ + "internal/cli/schema_command_registry.json#chat.shortcut_messages_send_card", + "cobra-help:dws chat +messages-send-card", + "ShortcutRegistry:chat +messages-send-card" + ] + }, "chat.shortcut_messages_list_direct": { "agent_summary": "拉取单聊会话消息", "use_when": [ @@ -2170,7 +2230,7 @@ "chat.shortcut_messages_mget": { "agent_summary": "根据消息 ID 批量查询消息(最多 50 条)", "use_when": [ - "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" + "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" ], "avoid_when": [ "需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令" @@ -2359,6 +2419,26 @@ "ShortcutRegistry:chat +my-groups" ] }, + "chat.shortcut_search_msg": { + "agent_summary": "多维搜索消息,可全量翻页并批量富化详情", + "use_when": [ + "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。" + ], + "avoid_when": [ + "只想读取一个已知会话的连续历史时使用 +chat-messages;已有精确消息 ID 时使用 +messages-mget" + ], + "examples": [ + "dws chat +search-msg --query \"周报\" --senders --days 3 --page-all", + "dws chat +search-msg --group --message-type file --download-resources --output-dir ./downloads" + ], + "reviewed": true, + "review_reason": "Agent-authored from the verified filter mapping, cursor pagination, batched mget enrichment, completeness ledger, and shared safe resource workflow.", + "source_refs": [ + "internal/cli/schema_command_registry.json#chat.shortcut_search_msg", + "cobra-help:dws chat +search-msg", + "ShortcutRegistry:chat +search-msg" + ] + }, "chat.shortcut_send_to_group": { "agent_summary": "按群名直接给群发消息(自动搜群解析 openConversationId)", "use_when": [ @@ -2378,6 +2458,26 @@ "ShortcutRegistry:chat +send-to-group" ] }, + "chat.shortcut_thread_replies": { + "agent_summary": "拉取某条话题消息的全部回复并投影出发言人/文本/时间", + "use_when": [ + "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + ], + "avoid_when": [ + "要回复 Thread 或发送新回复时不要使用此只读入口;当前没有经过验证的 thread writer Shortcut" + ], + "examples": [ + "dws chat +thread-replies --group --thread-id ", + "dws chat +thread-replies --group --topic-id --download-resources --output-dir ./downloads" + ], + "reviewed": true, + "review_reason": "Agent-authored from the verified thread/topic alias, lower list_topic_replies mapping, stable projection, group-context fallback, and optional safe resource workflow.", + "source_refs": [ + "internal/cli/schema_command_registry.json#chat.shortcut_thread_replies", + "cobra-help:dws chat +thread-replies", + "ShortcutRegistry:chat +thread-replies" + ] + }, "chat.shortcut_unread_chats": { "agent_summary": "列出我有未读消息的会话(投影会话名/未读数/会话ID)", "use_when": [ diff --git a/internal/helpers/chat_file_shortcut.go b/internal/helpers/chat_file_shortcut.go new file mode 100644 index 00000000..9d97c47e --- /dev/null +++ b/internal/helpers/chat_file_shortcut.go @@ -0,0 +1,43 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. + +package helpers + +import "context" + +// ConversationLocalFileMeta exposes the already-reviewed native chat upload +// metadata to built-in semantic Shortcuts. It remains an alias so the native +// Cobra leaf and Shortcut use exactly the same upload implementation. +type ConversationLocalFileMeta = conversationLocalFileMeta + +// BuildConversationLocalFileMeta validates a local file and computes the +// metadata required by DingTalk's conversation-file upload flow. +func BuildConversationLocalFileMeta(filePath, fileName, md5Value string) (ConversationLocalFileMeta, error) { + return buildConversationLocalFileMeta(filePath, fileName, md5Value) +} + +// UploadConversationLocalFile executes the existing init -> HTTP upload -> +// commit flow and returns the commit response for message-content assembly. +func UploadConversationLocalFile( + ctx context.Context, + targetArgs map[string]any, + meta ConversationLocalFileMeta, + uuid string, +) (string, error) { + return uploadConversationLocalFile(ctx, targetArgs, meta, uuid) +} + +// ParseConversationFileSendIDs extracts the committed dentry and space IDs. +func ParseConversationFileSendIDs(text string) (int64, int64, error) { + return parseConversationFileSendIDs(text) +} + +// BuildConversationFileContent renders the exact file-message content accepted +// by send_personal_message. +func BuildConversationFileContent( + dentryID, spaceID int64, + meta ConversationLocalFileMeta, +) (string, error) { + return buildConversationFileContent(dentryID, spaceID, meta) +} diff --git a/internal/helpers/gap_fill_test.go b/internal/helpers/gap_fill_test.go new file mode 100644 index 00000000..ac727a7f --- /dev/null +++ b/internal/helpers/gap_fill_test.go @@ -0,0 +1,87 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); + +package helpers + +import ( + "context" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestCrossPlatformCoverageConversationFileShortcutWrappersReuseNativeFlow(t *testing.T) { + file := filepath.Join(t.TempDir(), "report.txt") + if err := os.WriteFile(file, []byte("content"), 0o600); err != nil { + t.Fatal(err) + } + meta, err := BuildConversationLocalFileMeta(file, "", "") + if err != nil { + t.Fatal(err) + } + if meta.FileName != "report.txt" || meta.FileSize != 7 { + t.Fatalf("meta = %#v", meta) + } + content, err := BuildConversationFileContent(11, 22, meta) + if err != nil || !strings.Contains(content, `"dentryId":11`) { + t.Fatalf("content = %q, %v", content, err) + } + dentryID, spaceID, err := ParseConversationFileSendIDs(`{"result":{"dentryId":11,"spaceId":22}}`) + if err != nil || dentryID != 11 || spaceID != 22 { + t.Fatalf("ids = %d/%d, %v", dentryID, spaceID, err) + } + + oldPut := httpPutFile + t.Cleanup(func() { httpPutFile = oldPut }) + var putPath string + httpPutFile = func(_ context.Context, _ string, _ map[string]string, localPath string, size int64) error { + putPath = localPath + if size != 7 { + t.Errorf("upload size = %d", size) + } + return nil + } + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"resourceUrl":"https://upload.example.test/file","uploadKey":"key"}`}, + {text: `{"result":{"dentryId":11,"spaceId":22}}`}, + }} + installScriptedCaller(t, caller) + commit, err := UploadConversationLocalFile( + context.Background(), + map[string]any{"openConversationId": "cid"}, + meta, + "uuid", + ) + if err != nil || !strings.Contains(commit, `"dentryId":11`) || putPath != file || caller.calls != 2 { + t.Fatalf("upload = %q, path=%q calls=%d err=%v", commit, putPath, caller.calls, err) + } +} + +func TestCrossPlatformCoverageReadToolNameContractAndHelperBoundary(t *testing.T) { + for tool, want := range map[string]bool{ + " get_conversation ": true, + "LIST_MESSAGES": true, + "query_send_status": true, + "search_messages": true, + "unread_message_conversation_list": true, + "send_personal_message": false, + "": false, + } { + if got := IsReadToolName(tool); got != want { + t.Errorf("IsReadToolName(%q) = %v, want %v", tool, got, want) + } + } + + caller := &helpersReadCaller{ + helpersCoreCaller: &helpersCoreCaller{format: "json", dry: true}, + readResult: textToolResult(`{"success":true}`), + } + installHelpersCoreDeps(t, caller) + if _, err := CallMCPReadToolTextOnServer("chat", "send_personal_message", nil); err == nil { + t.Fatal("write tool was accepted by the read helper boundary") + } + if caller.readCalls != 0 || caller.calls != 0 { + t.Fatalf("rejected write reached caller: read=%d regular=%d", caller.readCalls, caller.calls) + } +} diff --git a/internal/helpers/helpers.go b/internal/helpers/helpers.go index acde24fb..4daa30f2 100644 --- a/internal/helpers/helpers.go +++ b/internal/helpers/helpers.go @@ -156,6 +156,12 @@ func callMCPToolReturnTextOnServer(ctx context.Context, serverID, toolName strin // returning a synthetic dry-run envelope that looks like business data. func CallMCPReadToolTextOnServer(serverID, toolName string, args map[string]any) (string, error) { ctx := context.Background() + if !IsReadToolName(toolName) { + return "", &CLIError{ + Code: CodeMCPToolError, + Message: fmt.Sprintf("tool %q is not allowed on the dry-run read channel", toolName), + } + } if deps == nil || deps.Caller == nil { return "", &CLIError{ Code: CodeMCPToolError, @@ -176,6 +182,22 @@ func CallMCPReadToolTextOnServer(serverID, toolName string, args map[string]any) return parseMCPToolTextResult(serverID, toolName, result, err) } +// IsReadToolName is the fail-closed naming contract for the dry-run read +// channel. Both the Shortcut runtime and the helper boundary enforce it so a +// future direct helper caller cannot accidentally route a write tool through +// ReadToolCaller. +func IsReadToolName(toolName string) bool { + toolName = strings.TrimSpace(strings.ToLower(toolName)) + for _, prefix := range []string{ + "get_", "list_", "query_", "search_", "unread_", + } { + if strings.HasPrefix(toolName, prefix) { + return true + } + } + return false +} + func parseMCPToolTextResult(serverID, toolName string, result *edition.ToolResult, err error) (string, error) { if err != nil { if patErr := reclassifyPATFromError(err); patErr != nil { diff --git a/internal/shortcut/chat/chat_message.go b/internal/shortcut/chat/chat_message.go index 12a385b1..ed371b9f 100644 --- a/internal/shortcut/chat/chat_message.go +++ b/internal/shortcut/chat/chat_message.go @@ -14,6 +14,7 @@ package chat import ( + "encoding/json" "fmt" "os" "path/filepath" @@ -525,39 +526,26 @@ var MessagesMget = shortcut.Shortcut{ Command: "+messages-mget", Product: "im", Description: "根据消息 ID 批量查询消息(最多 50 条)", - Intent: "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。", + Intent: "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。", Risk: shortcut.RiskRead, - Flags: []shortcut.Flag{ + Flags: append([]shortcut.Flag{ {Name: "msg-ids", Type: shortcut.FlagStringSlice, Desc: "消息 openMsgId 列表;--msg-ids 去重后必须包含 1-50 条消息 ID", Required: true}, {Name: "no-reactions", Type: shortcut.FlagBool, Desc: "不输出消息 reaction(默认输出)"}, - {Name: "download-resources", Type: shortcut.FlagBool, Desc: "自动下载消息中的全部可识别 mediaId 资源"}, - {Name: "output-dir", Type: shortcut.FlagString, Default: "./downloads", Desc: "资源输出目录;--output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸"}, - {Name: "overwrite", Type: shortcut.FlagBool, Desc: "允许覆盖同名资源文件(默认拒绝)"}, - }, - Constraints: []shortcut.Constraint{ + }, MessageResourceDownloadFlags()...), + Constraints: append([]shortcut.Constraint{ { Kind: shortcut.ConstraintCustom, Flags: []string{"msg-ids"}, Description: "--msg-ids 去重后必须包含 1-50 条消息 ID", }, - { - Kind: shortcut.ConstraintCustom, - Flags: []string{"output-dir"}, - Description: "--output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", - }, - }, + }, MessageResourceDownloadConstraints()...), Tips: []string{`dws chat +messages-mget --msg-ids msgId1,msgId2`}, Validate: func(rt *shortcut.RuntimeContext) error { ids := uniqueShortcutStrings(rt.StrSlice("msg-ids")) if len(ids) < 1 || len(ids) > 50 { return fmt.Errorf("--msg-ids 去重后必须包含 1-50 条消息 ID,当前 %d 条", len(ids)) } - if rt.Bool("download-resources") { - if err := validateResourceDownloadOutputFlag(rt.Str("output-dir"), "--output-dir"); err != nil { - return err - } - } - return nil + return ValidateMessageResourceDownload(rt) }, Execute: func(rt *shortcut.RuntimeContext) error { ids := uniqueShortcutStrings(rt.StrSlice("msg-ids")) @@ -587,81 +575,179 @@ var MessagesMget = shortcut.Shortcut{ "messages": messages, } if rt.Bool("download-resources") { - ledger, err := downloadMgetResources(rt, rawMessages) - if err != nil { - return err - } - payload["resourceDownloads"] = ledger + payload["resourceDownloads"] = DownloadMessageResources(rt, rawMessages, "") } return rt.Output(payload) }, } -func downloadMgetResources(rt *shortcut.RuntimeContext, messages []map[string]any) (map[string]any, error) { +// MessageResourceDownloadFlags returns the common opt-in resource workflow used +// by message list, search, mget, @me and thread-reading Shortcuts. +func MessageResourceDownloadFlags() []shortcut.Flag { + return []shortcut.Flag{ + {Name: "download-resources", Type: shortcut.FlagBool, Desc: "自动下载消息中的全部可识别 mediaId/fileId 资源"}, + {Name: "output-dir", Type: shortcut.FlagString, Default: "./downloads", Desc: "资源输出目录;必须是工作目录内的相对路径,禁止绝对路径和 .. 逃逸"}, + {Name: "overwrite", Type: shortcut.FlagBool, Desc: "允许覆盖同名资源文件(默认拒绝)"}, + } +} + +// MessageResourceDownloadConstraints publishes the shared safe-output rule. +func MessageResourceDownloadConstraints() []shortcut.Constraint { + return []shortcut.Constraint{{ + Kind: shortcut.ConstraintCustom, + Flags: []string{"output-dir"}, + Description: "--output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", + }} +} + +// ValidateMessageResourceDownload validates the output path only when the +// caller opts into local writes. +func ValidateMessageResourceDownload(rt *shortcut.RuntimeContext) error { + if !rt.Bool("download-resources") { + return nil + } + return validateResourceDownloadOutputFlag(rt.Str("output-dir"), "--output-dir") +} + +// DownloadMessageResources downloads every unique message resource reference +// and returns a per-resource success/failure ledger. A fallback conversation +// ID lets group/thread list commands supply context when a mediaId item's lower +// response omits it; fileId resources route through the existing drive leaf. +func DownloadMessageResources( + rt *shortcut.RuntimeContext, + messages []map[string]any, + fallbackConversationID string, +) map[string]any { resources := make([]map[string]any, 0) for _, message := range messages { resources = append(resources, chatmsg.Resources(message)...) } + discoveredCount := len(resources) + uniqueResources := make([]map[string]any, 0, len(resources)) + seen := map[string]bool{} + for _, resource := range resources { + resourceType := strings.TrimSpace(fmt.Sprint(resource["type"])) + resourceID := strings.TrimSpace(fmt.Sprint(resource["resourceId"])) + download, _ := resource["download"].(map[string]any) + arguments, _ := download["arguments"].(map[string]any) + messageID := strings.TrimSpace(fmt.Sprint(arguments["message-id"])) + if messageID == "" { + messageID = "" + } + key := strings.ToLower(resourceType) + "\x00" + resourceID + if resourceID != "" && resourceID != "" { + if seen[key] { + continue + } + seen[key] = true + } + uniqueResources = append(uniqueResources, resource) + } + resources = uniqueResources if rt.DryRun() { return map[string]any{ - "dryRun": true, - "requestedCount": len(resources), - "resources": resources, - }, nil + "dryRun": true, + "discoveredCount": discoveredCount, + "requestedCount": len(resources), + "deduplicatedCount": discoveredCount - len(resources), + "resources": resources, + } } cwd, err := resourceGetwd() if err != nil { - return nil, fmt.Errorf("读取工作目录失败: %w", err) + return map[string]any{ + "ok": false, + "partial": false, + "discoveredCount": discoveredCount, + "requestedCount": len(resources), + "deduplicatedCount": discoveredCount - len(resources), + "downloadedCount": 0, + "failedCount": 1, + "downloads": []map[string]any{}, + "failures": []map[string]any{{ + "stage": "output-directory", + "error": fmt.Sprintf("读取工作目录失败: %v", err), + }}, + } } outputDir := strings.TrimRight(rt.Str("output-dir"), `/\`) + string(os.PathSeparator) downloads := make([]map[string]any, 0, len(resources)) failures := make([]map[string]any, 0) + plannedNames := map[string]int{} for _, resource := range resources { + resourceType := strings.TrimSpace(fmt.Sprint(resource["type"])) resourceID := strings.TrimSpace(fmt.Sprint(resource["resourceId"])) download, _ := resource["download"].(map[string]any) arguments, _ := download["arguments"].(map[string]any) messageID := strings.TrimSpace(fmt.Sprint(arguments["message-id"])) conversationID := strings.TrimSpace(fmt.Sprint(arguments["open-conversation-id"])) - if download["ready"] != true || resourceID == "" || messageID == "" || conversationID == "" { + if messageID == "" { + messageID = "" + } + if conversationID == "" || conversationID == "" { + conversationID = strings.TrimSpace(fallbackConversationID) + } + missingMediaContext := resourceType == "mediaId" && + (messageID == "" || messageID == "" || + conversationID == "" || conversationID == "") + if resourceID == "" || resourceID == "" || missingMediaContext { failures = append(failures, map[string]any{ - "resourceId": resourceID, - "messageId": messageID, - "error": "资源引用缺少 message-id 或 open-conversation-id", + "resourceType": resourceType, + "resourceId": resourceID, + "messageId": messageID, + "error": "资源引用缺少 resource-id,或 mediaId 缺少 message-id/open-conversation-id", }) continue } - data, callErr := rt.CallMCPData("im", "get_resource_download_url", map[string]any{ - "resourceType": "mediaId", - "resourceId": resourceID, - "openMessageId": messageID, - "openConversationId": conversationID, - }) + data, callErr := resolveMessageResourceDownloadData( + rt, resourceType, resourceID, messageID, conversationID) if callErr != nil { failures = append(failures, map[string]any{ - "resourceId": resourceID, - "messageId": messageID, - "error": callErr.Error(), + "resourceType": resourceType, + "resourceId": resourceID, + "messageId": messageID, + "error": callErr.Error(), }) continue } resourceURL, headers, infoErr := resourceDownloadInfo(data) if infoErr != nil { failures = append(failures, map[string]any{ - "resourceId": resourceID, - "messageId": messageID, - "error": infoErr.Error(), + "resourceType": resourceType, + "resourceId": resourceID, + "messageId": messageID, + "error": infoErr.Error(), }) continue } + preferredName := resourceDownloadPreferredName(data) + output := outputDir + filename := resourceDownloadFilename(resourceURL, preferredName) + filenameKey := strings.ToLower(filename) + plannedNames[filenameKey]++ + if plannedNames[filenameKey] > 1 { + extension := filepath.Ext(filename) + output = filepath.Join( + strings.TrimRight(outputDir, `/\`), + strings.TrimSuffix(filename, extension)+ + fmt.Sprintf(" (%d)", plannedNames[filenameKey])+extension, + ) + } destPath, relativePath, pathErr := resolveResourceDownloadPath( - cwd, outputDir, resourceURL, rt.Bool("overwrite")) + cwd, + output, + resourceURL, + rt.Bool("overwrite"), + preferredName, + ) if pathErr != nil { failures = append(failures, map[string]any{ - "resourceId": resourceID, - "messageId": messageID, - "error": pathErr.Error(), + "resourceType": resourceType, + "resourceId": resourceID, + "messageId": messageID, + "error": pathErr.Error(), }) continue } @@ -669,28 +755,32 @@ func downloadMgetResources(rt *shortcut.RuntimeContext, messages []map[string]an rt.Command().Context(), nil, resourceURL, headers, destPath, rt.Bool("overwrite")) if downloadErr != nil { failures = append(failures, map[string]any{ - "resourceId": resourceID, - "messageId": messageID, - "error": downloadErr.Error(), + "resourceType": resourceType, + "resourceId": resourceID, + "messageId": messageID, + "error": downloadErr.Error(), }) continue } downloads = append(downloads, map[string]any{ - "resourceId": resourceID, - "messageId": messageID, - "localPath": filepath.ToSlash(relativePath), - "sizeBytes": size, + "resourceType": resourceType, + "resourceId": resourceID, + "messageId": messageID, + "localPath": filepath.ToSlash(relativePath), + "sizeBytes": size, }) } return map[string]any{ - "ok": len(failures) == 0, - "partial": len(downloads) > 0 && len(failures) > 0, - "requestedCount": len(resources), - "downloadedCount": len(downloads), - "failedCount": len(failures), - "downloads": downloads, - "failures": failures, - }, nil + "ok": len(failures) == 0, + "partial": len(downloads) > 0 && len(failures) > 0, + "discoveredCount": discoveredCount, + "requestedCount": len(resources), + "deduplicatedCount": discoveredCount - len(resources), + "downloadedCount": len(downloads), + "failedCount": len(failures), + "downloads": downloads, + "failures": failures, + } } func uniqueShortcutStrings(values []string) []string { @@ -888,22 +978,42 @@ var MessagesCreateTextEmotion = shortcut.Shortcut{ }, } -// MessagesSendCard creates and pushes a streaming card (create_and_send_card, im). +// MessagesSendCard creates and optionally completes a streaming card by +// composing create_and_send_card with update_streaming_card. var MessagesSendCard = shortcut.Shortcut{ Service: "chat", Command: "+messages-send-card", Product: "im", - Description: "创建并推送流式卡片(需配合 messages-update-card)", - Intent: "当你要发送一张可后续流式更新的卡片消息(如 AI 逐字输出)时使用;会实际推送卡片并返回 bizId,群 openConversationId 或单聊接收者 userId 二选一,配合 messages-update-card 更新。", + Description: "创建流式卡片,可在同一次调用中写入内容并结束", + Intent: "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId 二选一。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。", Risk: shortcut.RiskWrite, Flags: []shortcut.Flag{ {Name: "group", Type: shortcut.FlagString, Desc: "群 openConversationId(与 --receiver 互斥)"}, {Name: "receiver", Type: shortcut.FlagString, Desc: "单聊接收者 userId(与 --group 互斥)"}, + {Name: "content", Type: shortcut.FlagString, Desc: "创建后立即写入的卡片内容;省略时仅创建并返回 bizId"}, + {Name: "flow-status", Type: shortcut.FlagInt, Default: "3", Desc: "自动更新状态:1处理中/2输入中/3完成/4执行中/5错误;--flow-status 必须在 1-5 之间,且显式指定时必须同时提供 --content"}, }, Constraints: []shortcut.Constraint{ {Kind: shortcut.ConstraintExactlyOne, Flags: []string{"group", "receiver"}}, + { + Kind: shortcut.ConstraintCustom, + Flags: []string{"flow-status"}, + Description: "--flow-status 必须在 1-5 之间,且显式指定时必须同时提供 --content", + }, + }, + Tips: []string{ + `dws chat +messages-send-card --group `, + `dws chat +messages-send-card --group --content "任务已完成"`, + }, + Validate: func(rt *shortcut.RuntimeContext) error { + if status := rt.Int("flow-status"); status < 1 || status > 5 { + return fmt.Errorf("--flow-status 必须在 1-5 之间") + } + if rt.Changed("flow-status") && rt.Str("content") == "" { + return fmt.Errorf("--flow-status 只有与 --content 一起使用才有意义") + } + return nil }, - Tips: []string{`dws chat +messages-send-card --group `}, Execute: func(rt *shortcut.RuntimeContext) error { group := rt.Str("group") receiver := rt.Str("receiver") @@ -919,10 +1029,91 @@ var MessagesSendCard = shortcut.Shortcut{ } else { params["receiverUid"] = receiver } - return rt.CallMCP("create_and_send_card", params) + content := rt.Str("content") + if content == "" { + return rt.CallMCP("create_and_send_card", params) + } + status := rt.Int("flow-status") + if rt.DryRun() { + return rt.Output(map[string]any{ + "dry_run": true, + "executed": false, + "preview_kind": "plan", + "actionCount": 2, + "failedCount": 0, + "actions": []map[string]any{ + { + "tool": "create_and_send_card", + "arguments": params, + }, + { + "tool": "update_streaming_card", + "arguments": map[string]any{ + "bizId": "", + "msgContent": content, + "flowStatus": status, + }, + }, + }, + }) + } + created, err := rt.CallMCPWriteData("im", "create_and_send_card", params) + if err != nil { + return err + } + bizID := findCardBizID(created) + if bizID == "" { + return fmt.Errorf("卡片已创建但下层未返回 bizId,无法自动更新;请检查 create_and_send_card 响应") + } + updated, err := rt.CallMCPWriteData("im", "update_streaming_card", map[string]any{ + "bizId": bizID, + "msgContent": content, + "flowStatus": status, + }) + if err != nil { + return fmt.Errorf("卡片已创建(bizId=%s),但自动更新失败: %w", bizID, err) + } + return rt.Output(map[string]any{ + "ok": true, + "bizId": bizID, + "flowStatus": status, + "created": created, + "updated": updated, + }) }, } +func findCardBizID(value any) string { + switch typed := value.(type) { + case map[string]any: + for _, key := range []string{"bizId", "bizID", "biz_id"} { + if candidate := strings.TrimSpace(fmt.Sprint(typed[key])); candidate != "" && candidate != "" { + return candidate + } + } + for _, child := range typed { + if candidate := findCardBizID(child); candidate != "" { + return candidate + } + } + case []any: + for _, child := range typed { + if candidate := findCardBizID(child); candidate != "" { + return candidate + } + } + case string: + trimmed := strings.TrimSpace(typed) + if strings.HasPrefix(trimmed, "{") || strings.HasPrefix(trimmed, "[") { + var nested any + if json.Unmarshal([]byte(trimmed), &nested) == nil { + return findCardBizID(nested) + } + } + } + return "" +} + // MessagesUpdateCard streams updated card content (update_streaming_card, im). var MessagesUpdateCard = shortcut.Shortcut{ Service: "chat", diff --git a/internal/shortcut/chat/chat_message_coverage_test.go b/internal/shortcut/chat/chat_message_coverage_test.go index 812d7b21..c4539e1d 100644 --- a/internal/shortcut/chat/chat_message_coverage_test.go +++ b/internal/shortcut/chat/chat_message_coverage_test.go @@ -7,7 +7,6 @@ import ( "context" "errors" "net/http" - "strings" "testing" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/helpers" @@ -63,8 +62,8 @@ func TestCrossPlatformCoverageMgetResourceDownloadOutcomes(t *testing.T) { }}) root := newPlatformCoverageRoot() root.SetArgs(baseArgs) - if err := root.Execute(); err == nil || !strings.Contains(err.Error(), "工作目录") { - t.Fatalf("getwd error = %v", err) + if err := root.Execute(); err != nil { + t.Fatalf("getwd ledger error = %v", err) } }) diff --git a/internal/shortcut/chat/gap_fill_test.go b/internal/shortcut/chat/gap_fill_test.go new file mode 100644 index 00000000..e81275eb --- /dev/null +++ b/internal/shortcut/chat/gap_fill_test.go @@ -0,0 +1,667 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); + +package chat + +import ( + "bytes" + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/helpers" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut" +) + +func TestCrossPlatformCoverageMessagesSendCurrentUserImageAndUserResolution(t *testing.T) { + fake := &larkAlignmentCaller{} + helpers.InitDeps(fake) + root := newPlatformCoverageRoot() + root.SetArgs([]string{ + "chat", "+messages-send", + "--user", "user-id", + "--msg-type", "image", + "--media-id", "@image", + "--idempotency-key", "image-key", + "--yes", + }) + if err := root.Execute(); err != nil { + t.Fatal(err) + } + if len(fake.calls) != 2 { + t.Fatalf("calls = %#v, want contact resolution + send", fake.calls) + } + if fake.calls[0].product != "contact" || fake.calls[0].tool != "get_user_info_by_user_ids" { + t.Fatalf("resolution call = %#v", fake.calls[0]) + } + send := fake.calls[1] + if send.product != "chat" || send.tool != "send_personal_message" { + t.Fatalf("send call = %#v", send) + } + for key, want := range map[string]any{ + "receiverOpenDingTalkId": "D-resolved", + "msgType": "image", + "uuid": "image-key", + } { + if !reflect.DeepEqual(send.args[key], want) { + t.Errorf("%s = %#v, want %#v", key, send.args[key], want) + } + } + var content map[string]string + if err := json.Unmarshal([]byte(send.args["content"].(string)), &content); err != nil { + t.Fatal(err) + } + if content["mediaId"] != "@image" { + t.Fatalf("image content = %#v", content) + } +} + +func TestCrossPlatformCoverageMessagesSendCurrentUserLocalFileFlow(t *testing.T) { + t.Chdir(t.TempDir()) + if err := os.WriteFile("report.txt", []byte("gap-fill"), 0o600); err != nil { + t.Fatal(err) + } + var uploaded []byte + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPut { + t.Errorf("upload method = %s", r.Method) + } + var err error + uploaded, err = io.ReadAll(r.Body) + if err != nil { + t.Error(err) + } + w.WriteHeader(http.StatusOK) + })) + t.Cleanup(server.Close) + + fake := &larkAlignmentCaller{responses: map[string]string{ + "im/init_conversation_file_upload": `{"resourceUrl":"` + server.URL + `","uploadKey":"upload-key"}`, + "im/commit_conversation_file_upload": `{"result":{"dentryId":11,"spaceId":22}}`, + "chat/send_personal_message": `{"result":{"openMessageId":"sent-file"}}`, + }} + helpers.InitDeps(fake) + root := newPlatformCoverageRoot() + var output bytes.Buffer + root.SetOut(&output) + root.SetArgs([]string{ + "chat", "+messages-send", + "--group", "cid", + "--msg-type", "audio", + "--file-path", "./report.txt", + "--uuid", "file-key", + "--yes", + }) + if err := root.Execute(); err != nil { + t.Fatal(err) + } + if string(uploaded) != "gap-fill" || len(fake.calls) != 3 { + t.Fatalf("uploaded = %q, calls = %#v", uploaded, fake.calls) + } + if fake.calls[0].tool != "init_conversation_file_upload" || + fake.calls[1].tool != "commit_conversation_file_upload" || + fake.calls[2].tool != "send_personal_message" { + t.Fatalf("file flow calls = %#v", fake.calls) + } + send := fake.calls[2] + if send.args["msgType"] != "file" || send.args["openConversationId"] != "cid" || + send.args["uuid"] != "file-key" { + t.Fatalf("file send args = %#v", send.args) + } + var content map[string]any + if err := json.Unmarshal([]byte(send.args["content"].(string)), &content); err != nil { + t.Fatal(err) + } + if content["dentryId"] != float64(11) || content["spaceId"] != float64(22) || + content["fileName"] != "report.txt" { + t.Fatalf("file content = %#v", content) + } + var payload map[string]any + if err := json.Unmarshal(output.Bytes(), &payload); err != nil { + t.Fatal(err) + } + if payload["requestedMessageType"] != "audio" || payload["effectiveMessageType"] != "file" { + t.Fatalf("file output = %#v", payload) + } +} + +func TestCrossPlatformCoverageMessagesSendCurrentUserLocalFileDryRunAndFailures(t *testing.T) { + t.Chdir(t.TempDir()) + if err := os.WriteFile("fixture.bin", []byte("x"), 0o600); err != nil { + t.Fatal(err) + } + uploadServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + })) + t.Cleanup(uploadServer.Close) + fake := &larkAlignmentCaller{} + helpers.InitDeps(fake) + root := newPlatformCoverageRoot() + var output bytes.Buffer + root.SetOut(&output) + root.SetArgs([]string{ + "chat", "+messages-send", + "--open-dingtalk-id", "D-target", + "--file", "./fixture.bin", + "--dry-run", + "--yes", + }) + if err := root.Execute(); err != nil { + t.Fatal(err) + } + if len(fake.calls) != 0 { + t.Fatalf("file dry-run made lower calls: %#v", fake.calls) + } + var payload map[string]any + if err := json.Unmarshal(output.Bytes(), &payload); err != nil { + t.Fatal(err) + } + if payload["actionCount"] != float64(2) || payload["preview_kind"] != "plan" { + t.Fatalf("dry-run payload = %#v", payload) + } + + for _, tc := range []struct { + name string + args []string + }{ + {"unsafe path", []string{"--group", "cid", "--file", "../outside"}}, + {"missing file", []string{"--group", "cid", "--file", "./missing"}}, + {"media mismatch", []string{"--group", "cid", "--media-id", "@image", "--msg-type", "file"}}, + {"file mismatch", []string{"--group", "cid", "--file", "./fixture.bin", "--msg-type", "image"}}, + {"missing media", []string{"--group", "cid", "--text", "x", "--msg-type", "image"}}, + {"bot media", []string{"--identity", "bot", "--robot-code", "robot", "--group", "cid", "--media-id", "@image"}}, + {"webhook media", []string{"--identity", "webhook", "--webhook-token", "token", "--media-id", "@image"}}, + {"user target conflict", []string{"--group", "cid", "--user", "u1", "--text", "x"}}, + {"user media at", []string{"--group", "cid", "--media-id", "@image", "--at-all"}}, + {"text flag with markdown type", []string{"--group", "cid", "--text", "x", "--msg-type", "markdown"}}, + {"markdown flag with text type", []string{"--group", "cid", "--markdown", "x", "--msg-type", "text"}}, + } { + t.Run(tc.name, func(t *testing.T) { + helpers.InitDeps(&larkAlignmentCaller{}) + command := newPlatformCoverageRoot() + args := append([]string{"chat", "+messages-send"}, tc.args...) + args = append(args, "--yes") + command.SetArgs(args) + if err := command.Execute(); err == nil { + t.Fatalf("invalid args succeeded: %v", tc.args) + } + }) + } + + for _, tc := range []struct { + name string + fake *larkAlignmentCaller + }{ + { + name: "upload init error", + fake: &larkAlignmentCaller{failProductTool: "im/init_conversation_file_upload"}, + }, + { + name: "commit response missing IDs", + fake: &larkAlignmentCaller{responses: map[string]string{ + "im/init_conversation_file_upload": `{"resourceUrl":"` + uploadServer.URL + `","uploadKey":"key"}`, + "im/commit_conversation_file_upload": `{}`, + }}, + }, + { + name: "message send error", + fake: &larkAlignmentCaller{ + failProductTool: "chat/send_personal_message", + responses: map[string]string{ + "im/init_conversation_file_upload": `{"resourceUrl":"` + uploadServer.URL + `","uploadKey":"key"}`, + "im/commit_conversation_file_upload": `{"dentryId":1,"spaceId":2}`, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + helpers.InitDeps(tc.fake) + command := newPlatformCoverageRoot() + command.SetArgs([]string{ + "chat", "+messages-send", + "--group", "cid", + "--file", "./fixture.bin", + "--yes", + }) + if err := command.Execute(); err == nil { + t.Fatal("file failure scenario unexpectedly succeeded") + } + }) + } +} + +func TestCrossPlatformCoverageMessagesSendUserResolutionFailures(t *testing.T) { + for _, tc := range []struct { + name string + fake *larkAlignmentCaller + }{ + {"lookup error", &larkAlignmentCaller{failProductTool: "contact/get_user_info_by_user_ids"}}, + {"missing open id", &larkAlignmentCaller{responses: map[string]string{ + "contact/get_user_info_by_user_ids": `{"result":[]}`, + }}}, + } { + t.Run(tc.name, func(t *testing.T) { + helpers.InitDeps(tc.fake) + root := newPlatformCoverageRoot() + root.SetArgs([]string{ + "chat", "+messages-send", + "--user", "user-id", + "--text", "hello", + "--yes", + }) + if err := root.Execute(); err == nil { + t.Fatal("unresolved user was accepted") + } + if len(tc.fake.calls) != 1 { + t.Fatalf("calls = %#v", tc.fake.calls) + } + }) + } +} + +func TestCrossPlatformCoverageMessagesSendTextModesAndExecuteGuard(t *testing.T) { + for _, args := range [][]string{ + {"--group", "cid", "--markdown", "## markdown"}, + {"--group", "cid", "--text", "plain", "--msg-type", "text"}, + } { + fake := &larkAlignmentCaller{} + helpers.InitDeps(fake) + root := newPlatformCoverageRoot() + root.SetArgs(append(append([]string{"chat", "+messages-send"}, args...), "--yes")) + if err := root.Execute(); err != nil { + t.Fatal(err) + } + if len(fake.calls) != 1 || fake.calls[0].tool != "send_personal_message" { + t.Fatalf("text mode calls = %#v", fake.calls) + } + } + + shortcut.Register(shortcut.Shortcut{ + Service: "chat", + Command: "+gap-send-execute-guard", + Flags: MessagesSend.Flags, + Execute: executeMessagesSend, + }) + helpers.InitDeps(&larkAlignmentCaller{}) + root := newPlatformCoverageRoot() + root.SetArgs([]string{ + "chat", "+gap-send-execute-guard", + "--group", "cid", + "--media-id", "@image", + "--msg-type", "file", + }) + if err := root.Execute(); err == nil { + t.Fatal("execute-time content mismatch was accepted") + } +} + +func TestCrossPlatformCoverageMessagesSendCardOneCallLifecycle(t *testing.T) { + fake := &larkAlignmentCaller{responses: map[string]string{ + "im/create_and_send_card": `{"result":{"card":{"biz_id":"biz-1"}}}`, + "im/update_streaming_card": `{"result":{"updated":true}}`, + }} + helpers.InitDeps(fake) + root := newPlatformCoverageRoot() + var output bytes.Buffer + root.SetOut(&output) + root.SetArgs([]string{ + "chat", "+messages-send-card", + "--group", "cid", + "--content", "完成", + "--flow-status", "3", + "--yes", + }) + if err := root.Execute(); err != nil { + t.Fatal(err) + } + if len(fake.calls) != 2 || fake.calls[0].tool != "create_and_send_card" || + fake.calls[1].tool != "update_streaming_card" { + t.Fatalf("card calls = %#v", fake.calls) + } + if fake.calls[1].args["bizId"] != "biz-1" || fake.calls[1].args["msgContent"] != "完成" || + fake.calls[1].args["flowStatus"] != 3 { + t.Fatalf("card update args = %#v", fake.calls[1].args) + } + var payload map[string]any + if err := json.Unmarshal(output.Bytes(), &payload); err != nil { + t.Fatal(err) + } + if payload["bizId"] != "biz-1" || payload["ok"] != true { + t.Fatalf("card output = %#v", payload) + } +} + +func TestCrossPlatformCoverageMessagesSendCardDryRunAndFailureBoundaries(t *testing.T) { + t.Run("create only", func(t *testing.T) { + fake := &larkAlignmentCaller{} + helpers.InitDeps(fake) + root := newPlatformCoverageRoot() + root.SetArgs([]string{ + "chat", "+messages-send-card", + "--group", "cid", + "--yes", + }) + if err := root.Execute(); err != nil { + t.Fatal(err) + } + if len(fake.calls) != 1 || fake.calls[0].tool != "create_and_send_card" { + t.Fatalf("create-only calls = %#v", fake.calls) + } + }) + + t.Run("dry run", func(t *testing.T) { + fake := &larkAlignmentCaller{} + helpers.InitDeps(fake) + root := newPlatformCoverageRoot() + var output bytes.Buffer + root.SetOut(&output) + root.SetArgs([]string{ + "chat", "+messages-send-card", + "--receiver", "user", + "--content", "处理中", + "--flow-status", "1", + "--dry-run", + "--yes", + }) + if err := root.Execute(); err != nil { + t.Fatal(err) + } + if len(fake.calls) != 0 { + t.Fatalf("card dry-run made lower calls: %#v", fake.calls) + } + var payload map[string]any + if err := json.Unmarshal(output.Bytes(), &payload); err != nil { + t.Fatal(err) + } + if payload["actionCount"] != float64(2) { + t.Fatalf("card plan = %#v", payload) + } + }) + + for _, tc := range []struct { + name string + fake *larkAlignmentCaller + wantError string + }{ + { + name: "create error", + fake: &larkAlignmentCaller{failProductTool: "im/create_and_send_card"}, + wantError: "fixture lower call failed", + }, + { + name: "missing biz id", + fake: &larkAlignmentCaller{responses: map[string]string{ + "im/create_and_send_card": `{"result":{"created":true}}`, + }}, + wantError: "未返回 bizId", + }, + { + name: "update error preserves id", + fake: &larkAlignmentCaller{ + failProductTool: "im/update_streaming_card", + responses: map[string]string{ + "im/create_and_send_card": `{"bizId":"biz-preserved"}`, + }, + }, + wantError: "biz-preserved", + }, + } { + t.Run(tc.name, func(t *testing.T) { + helpers.InitDeps(tc.fake) + root := newPlatformCoverageRoot() + root.SetArgs([]string{ + "chat", "+messages-send-card", + "--group", "cid", + "--content", "完成", + "--yes", + }) + err := root.Execute() + if err == nil || !strings.Contains(err.Error(), tc.wantError) { + t.Fatalf("error = %v, want substring %q", err, tc.wantError) + } + }) + } + + for _, args := range [][]string{ + {"--group", "cid", "--content", "x", "--flow-status", "6"}, + {"--group", "cid", "--flow-status", "2"}, + } { + helpers.InitDeps(&larkAlignmentCaller{}) + root := newPlatformCoverageRoot() + root.SetArgs(append([]string{"chat", "+messages-send-card"}, args...)) + if err := root.Execute(); err == nil { + t.Fatalf("invalid card args succeeded: %v", args) + } + } +} + +func TestCrossPlatformCoverageFindCardBizIDResponseShapes(t *testing.T) { + for _, tc := range []struct { + value any + want string + }{ + {map[string]any{"bizId": "direct"}, "direct"}, + {map[string]any{"bizID": "caps"}, "caps"}, + {map[string]any{"biz_id": "snake"}, "snake"}, + {map[string]any{"result": []any{map[string]any{"bizId": "nested"}}}, "nested"}, + {`{"result":{"bizId":"json"}}`, "json"}, + {`[{"bizId":"array-json"}]`, "array-json"}, + {`{"bizId":`, ""}, + {"plain", ""}, + {42, ""}, + } { + if got := findCardBizID(tc.value); got != tc.want { + t.Errorf("findCardBizID(%#v) = %q, want %q", tc.value, got, tc.want) + } + } +} + +func TestCrossPlatformCoverageMessageResourceDownloadDeduplicatesAndUsesFallbackConversation(t *testing.T) { + resetResourceDownloadHooks(t) + t.Chdir(t.TempDir()) + resourceDownload = func( + _ context.Context, + _ *http.Client, + _ string, + _ map[string]string, + dest string, + _ bool, + ) (int64, error) { + if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil { + return 0, err + } + return 7, nil + } + fake := &larkAlignmentCaller{responses: map[string]string{ + "im/get_resource_download_url": `{"result":{"resourceUrl":"https://example.test/resource.bin"}}`, + }} + helpers.InitDeps(fake) + message := map[string]any{ + "openMessageId": "msg", + "content": `{"mediaId":"@same","nested":{"mediaId":"@same"}}`, + } + secondMessage := map[string]any{ + "openMessageId": "msg-2", + "content": `{"mediaId":"@same"}`, + } + var ledger map[string]any + shortcut.Register(shortcut.Shortcut{ + Service: "chat", + Command: "+gap-resource-download", + Flags: MessageResourceDownloadFlags(), + Execute: func(rt *shortcut.RuntimeContext) error { + ledger = DownloadMessageResources(rt, []map[string]any{message, secondMessage}, "cid-fallback") + return nil + }, + }) + root := newPlatformCoverageRoot() + root.SetArgs([]string{"chat", "+gap-resource-download", "--output-dir", "./downloads"}) + if err := root.Execute(); err != nil { + t.Fatal(err) + } + if ledger["discoveredCount"] != 2 || ledger["requestedCount"] != 1 || + ledger["deduplicatedCount"] != 1 || ledger["downloadedCount"] != 1 { + t.Fatalf("download ledger = %#v", ledger) + } + if len(fake.calls) != 1 || fake.calls[0].args["openConversationId"] != "cid-fallback" { + t.Fatalf("resource lookup = %#v", fake.calls) + } +} + +func TestCrossPlatformCoverageMessageFileResourceDownloadUsesDriveAndPreservesName(t *testing.T) { + resetResourceDownloadHooks(t) + t.Chdir(t.TempDir()) + resourceDownload = func( + _ context.Context, + _ *http.Client, + _ string, + _ map[string]string, + dest string, + _ bool, + ) (int64, error) { + if err := os.WriteFile(dest, []byte("drive-file"), 0o600); err != nil { + return 0, err + } + return int64(len("drive-file")), nil + } + fake := &larkAlignmentCaller{responses: map[string]string{ + "drive/download_file": `{"result":{"downloadUrl":"https://example.test/opaque","fileName":"fixture.txt"}}`, + }} + helpers.InitDeps(fake) + + root := newPlatformCoverageRoot() + root.SetArgs([]string{ + "chat", "+messages-resource-download", + "--type", "fileId", + "--resource-id", "drive-file", + "--output", "./downloads/", + }) + if err := root.Execute(); err != nil { + t.Fatal(err) + } + content, err := os.ReadFile(filepath.Join("downloads", "fixture.txt")) + if err != nil || string(content) != "drive-file" { + t.Fatalf("downloaded content = %q, err = %v", content, err) + } + if len(fake.calls) != 1 || + fake.calls[0].product != "drive" || + fake.calls[0].tool != "download_file" || + fake.calls[0].args["fileId"] != "drive-file" { + t.Fatalf("drive call = %#v", fake.calls) + } + + var ledger map[string]any + shortcut.Register(shortcut.Shortcut{ + Service: "chat", + Command: "+gap-file-resource-download", + Flags: MessageResourceDownloadFlags(), + Execute: func(rt *shortcut.RuntimeContext) error { + ledger = DownloadMessageResources(rt, []map[string]any{{ + "content": "[文件] fixture.txt fileId: drive-file", + }}, "") + return nil + }, + }) + root = newPlatformCoverageRoot() + root.SetArgs([]string{"chat", "+gap-file-resource-download", "--output-dir", "./batch-downloads"}) + if err := root.Execute(); err != nil { + t.Fatal(err) + } + if ledger["downloadedCount"] != 1 || ledger["failedCount"] != 0 { + t.Fatalf("file download ledger = %#v", ledger) + } + downloads := ledger["downloads"].([]map[string]any) + if downloads[0]["resourceType"] != "fileId" || + downloads[0]["localPath"] != "batch-downloads/fixture.txt" || + downloads[0]["messageId"] != "" { + t.Fatalf("file download = %#v", downloads[0]) + } +} + +func TestCrossPlatformCoverageMessageResourceDownloadDisambiguatesSameNames(t *testing.T) { + resetResourceDownloadHooks(t) + t.Chdir(t.TempDir()) + destinations := make([]string, 0, 2) + resourceDownload = func( + _ context.Context, + _ *http.Client, + _ string, + _ map[string]string, + dest string, + _ bool, + ) (int64, error) { + destinations = append(destinations, filepath.Base(dest)) + return 1, nil + } + helpers.InitDeps(&larkAlignmentCaller{responses: map[string]string{ + "drive/download_file": `{"result":{"downloadUrl":"https://example.test/opaque","fileName":"fixture.txt"}}`, + }}) + + var ledger map[string]any + shortcut.Register(shortcut.Shortcut{ + Service: "chat", + Command: "+gap-colliding-resource-download", + Flags: MessageResourceDownloadFlags(), + Execute: func(rt *shortcut.RuntimeContext) error { + ledger = DownloadMessageResources(rt, []map[string]any{ + {"fileId": "file-a"}, + {"fileId": "file-b"}, + }, "") + return nil + }, + }) + root := newPlatformCoverageRoot() + root.SetArgs([]string{"chat", "+gap-colliding-resource-download", "--output-dir", "./downloads"}) + if err := root.Execute(); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(destinations, []string{"fixture.txt", "fixture (2).txt"}) { + t.Fatalf("download destinations = %#v", destinations) + } + if ledger["downloadedCount"] != 2 || ledger["failedCount"] != 0 { + t.Fatalf("download ledger = %#v", ledger) + } +} + +func TestCrossPlatformCoverageMessageResourceDownloadRequiresMediaContextOnly(t *testing.T) { + helpers.InitDeps(&larkAlignmentCaller{}) + root := newPlatformCoverageRoot() + root.SetArgs([]string{ + "chat", "+messages-resource-download", + "--resource-id", "@media", + }) + if err := root.Execute(); err == nil || + !strings.Contains(err.Error(), "--type mediaId") { + t.Fatalf("missing media context error = %v", err) + } + + root = newPlatformCoverageRoot() + root.SetArgs([]string{ + "chat", "+messages-resource-download", + "--type", "fileId", + "--resource-id", "drive-file", + "--output", "../outside", + }) + if err := root.Execute(); err == nil || + !strings.Contains(err.Error(), "--output") { + t.Fatalf("unsafe output error = %v", err) + } +} + +func TestCrossPlatformCoverageReadHelperRejectsWriteToolDirectly(t *testing.T) { + fake := &larkAlignmentCaller{} + helpers.InitDeps(fake) + if _, err := helpers.CallMCPReadToolTextOnServer("chat", "send_personal_message", nil); err == nil { + t.Fatal("write tool was accepted by the helper read channel") + } + if len(fake.calls) != 0 { + t.Fatalf("rejected write reached lower caller: %#v", fake.calls) + } +} diff --git a/internal/shortcut/chat/resource_download.go b/internal/shortcut/chat/resource_download.go index 65e232cf..0abd9423 100644 --- a/internal/shortcut/chat/resource_download.go +++ b/internal/shortcut/chat/resource_download.go @@ -54,16 +54,16 @@ var MessagesResourceDownload = shortcut.Shortcut{ Service: "chat", Command: "+messages-resource-download", Product: "im", - Description: "安全下载消息资源(图片/视频/语音)到本地", - Intent: "当你需要拿到消息里的实际图片、视频或语音文件,而不只是临时 URL 时使用;" + - "先用消息和会话身份换取下载地址,再安全写入工作目录内的相对路径。" + + Description: "安全下载消息资源(图片/视频/语音/文件)到本地", + Intent: "当你需要拿到消息里的实际图片、视频、语音或钉盘文件,而不只是资源 ID 时使用;" + + "mediaId 用消息和会话身份换取下载地址,fileId 复用钉盘下载能力,再安全写入工作目录内的相对路径。" + "默认不覆盖已有文件,只有显式传 --overwrite 才覆盖。", Risk: shortcut.RiskRead, Flags: []shortcut.Flag{ - {Name: "type", Type: shortcut.FlagString, Default: "mediaId", Desc: "资源类型", Enum: []string{"mediaId"}}, - {Name: "resource-id", Type: shortcut.FlagString, Desc: "资源 ID(消息中的 mediaId)", Required: true}, - {Name: "message-id", Type: shortcut.FlagString, Desc: "消息 openMessageId", Required: true}, - {Name: "open-conversation-id", Type: shortcut.FlagString, Desc: "会话 openConversationId", Required: true}, + {Name: "type", Type: shortcut.FlagString, Default: "mediaId", Desc: "资源类型", Enum: []string{"mediaId", "fileId"}}, + {Name: "resource-id", Type: shortcut.FlagString, Desc: "消息中的 mediaId 或 fileId", Required: true}, + {Name: "message-id", Type: shortcut.FlagString, Desc: "mediaId 所属消息的 openMessageId"}, + {Name: "open-conversation-id", Type: shortcut.FlagString, Desc: "mediaId 所属会话的 openConversationId"}, {Name: "output", Type: shortcut.FlagString, Default: ".", Desc: "工作目录内的相对文件或目录路径"}, {Name: "overwrite", Type: shortcut.FlagBool, Desc: "允许覆盖已存在的目标文件(默认拒绝)"}, }, @@ -73,13 +73,28 @@ var MessagesResourceDownload = shortcut.Shortcut{ Flags: []string{"output"}, Description: "--output 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", }, + { + Kind: shortcut.ConstraintCustom, + Flags: []string{"type", "message-id", "open-conversation-id"}, + Description: "--type mediaId 时必须同时提供 --message-id 和 --open-conversation-id;fileId 不需要消息上下文", + }, }, Tips: []string{ `dws chat +messages-resource-download --resource-id --message-id --open-conversation-id `, + `dws chat +messages-resource-download --type fileId --resource-id --output ./downloads/`, `dws chat +messages-resource-download --resource-id --message-id --open-conversation-id --output ./downloads/`, }, Validate: func(rt *shortcut.RuntimeContext) error { - return validateResourceDownloadOutput(rt.Str("output")) + if err := validateResourceDownloadOutput(rt.Str("output")); err != nil { + return err + } + if rt.Str("type") == "mediaId" && + (strings.TrimSpace(rt.Str("message-id")) == "" || + strings.TrimSpace(rt.Str("open-conversation-id")) == "") { + return apperrors.NewValidation( + "--type mediaId 时必须同时提供 --message-id 和 --open-conversation-id") + } + return nil }, Execute: func(rt *shortcut.RuntimeContext) error { plan := map[string]any{ @@ -101,12 +116,13 @@ var MessagesResourceDownload = shortcut.Shortcut{ return rt.Output(plan) } - data, err := rt.CallMCPData("im", "get_resource_download_url", map[string]any{ - "resourceType": rt.Str("type"), - "resourceId": rt.Str("resource-id"), - "openMessageId": rt.Str("message-id"), - "openConversationId": rt.Str("open-conversation-id"), - }) + data, err := resolveMessageResourceDownloadData( + rt, + rt.Str("type"), + rt.Str("resource-id"), + rt.Str("message-id"), + rt.Str("open-conversation-id"), + ) if err != nil { return err } @@ -119,7 +135,12 @@ var MessagesResourceDownload = shortcut.Shortcut{ return apperrors.NewInternal(fmt.Sprintf("读取工作目录失败: %v", err)) } destPath, relativePath, err := resolveResourceDownloadPath( - cwd, rt.Str("output"), resourceURL, rt.Bool("overwrite")) + cwd, + rt.Str("output"), + resourceURL, + rt.Bool("overwrite"), + resourceDownloadPreferredName(data), + ) if err != nil { return err } @@ -138,6 +159,23 @@ var MessagesResourceDownload = shortcut.Shortcut{ }, } +func resolveMessageResourceDownloadData( + rt *shortcut.RuntimeContext, + resourceType, resourceID, messageID, conversationID string, +) (map[string]any, error) { + if resourceType == "fileId" { + return rt.CallMCPData("drive", "download_file", map[string]any{ + "fileId": resourceID, + }) + } + return rt.CallMCPData("im", "get_resource_download_url", map[string]any{ + "resourceType": "mediaId", + "resourceId": resourceID, + "openMessageId": messageID, + "openConversationId": conversationID, + }) +} + func validateResourceDownloadOutput(output string) error { return validateResourceDownloadOutputFlag(output, "--output") } @@ -211,6 +249,18 @@ func resourceDownloadInfo(data map[string]any) (string, map[string]string, error return resourceURL, headers, nil } +func resourceDownloadPreferredName(data map[string]any) string { + for { + result, ok := data["result"].(map[string]any) + if !ok { + break + } + data = result + } + name, _ := data["fileName"].(string) + return strings.TrimSpace(name) +} + func isAliyunOSSHost(host string) bool { host = strings.ToLower(strings.TrimSuffix(strings.TrimSpace(host), ".")) return host == "aliyuncs.com" || strings.HasSuffix(host, ".aliyuncs.com") @@ -219,6 +269,7 @@ func isAliyunOSSHost(host string) bool { func resolveResourceDownloadPath( baseDir, output, resourceURL string, overwrite bool, + preferredName ...string, ) (absolutePath, relativePath string, err error) { if err := validateResourceDownloadOutput(output); err != nil { return "", "", err @@ -242,7 +293,7 @@ func resolveResourceDownloadPath( directoryIntent || output == "." if isDirectory { - candidate = filepath.Join(candidate, resourceDownloadFilename(resourceURL)) + candidate = filepath.Join(candidate, resourceDownloadFilename(resourceURL, preferredName...)) } parent := filepath.Dir(candidate) @@ -321,7 +372,13 @@ func ensureResourceDownloadParent(baseDir, parent string) error { return nil } -func resourceDownloadFilename(resourceURL string) string { +func resourceDownloadFilename(resourceURL string, preferredName ...string) string { + if len(preferredName) > 0 { + name := filepath.Base(strings.ReplaceAll(strings.TrimSpace(preferredName[0]), "\\", "/")) + if name != "" && name != "." && name != string(os.PathSeparator) { + return name + } + } parsed, err := url.Parse(resourceURL) if err == nil { name, unescapeErr := url.PathUnescape(filepath.Base(parsed.Path)) diff --git a/internal/shortcut/chat/unified_send.go b/internal/shortcut/chat/unified_send.go index f9c51fc8..f8be9ca3 100644 --- a/internal/shortcut/chat/unified_send.go +++ b/internal/shortcut/chat/unified_send.go @@ -5,39 +5,49 @@ package chat import ( + "context" "encoding/json" "fmt" "strings" + "time" "unicode/utf8" apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/helpers" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut" ) -// MessagesSend is the identity-aware common text/Markdown entry point. Media -// upload remains on the native message send command until DWS has one shared -// upload contract; this Shortcut does not pretend webhook/bot/current-user -// transports support identical capabilities. +const messagesSendFileUploadTimeout = 10 * time.Minute + +// MessagesSend is the identity-aware common sending entry point. The current +// user branch reuses the native message leaf's reviewed file-upload flow and +// existing-mediaId image path. Bot and webhook remain text/Markdown-only +// because their lower transports do not expose equivalent media contracts. var MessagesSend = shortcut.Shortcut{ Service: "chat", Command: "+messages-send", Product: "chat", - Description: "统一发送文本/Markdown(current user、bot、webhook)", - Intent: "当你希望用同一个入口选择 current-user、bot 或 webhook 身份发送文本/Markdown 时使用;命令会按身份校验目标和凭据并路由到真实下层。current-user 支持幂等键,bot 支持群聊或批量单聊,webhook 的目标由 token 所在群决定。媒体上传仍使用原生命令。", + Description: "统一发送文本、Markdown、当前用户文件或已有 mediaId 图片", + Intent: "当你希望用同一个入口选择 current-user、bot 或 webhook 身份发送消息时使用;命令会按身份校验目标、内容和凭据并路由到真实下层。current-user 支持文本/Markdown、已有 mediaId 图片、安全相对路径文件上传和幂等键;bot 支持群聊或批量单聊文本/Markdown;webhook 的目标由 token 所在群决定。不会把 user 文件能力伪装成 bot/webhook 等价能力。", Risk: shortcut.RiskWrite, Flags: []shortcut.Flag{ - {Name: "identity", Type: shortcut.FlagString, Default: "user", Enum: []string{"user", "bot", "webhook"}, Desc: "发送身份"}, + {Name: "identity", Type: shortcut.FlagString, Default: "user", Enum: []string{"user", "bot", "webhook"}, Desc: "发送身份;身份相关目标、内容和凭据不能为空,完整能力矩阵见命令说明"}, {Name: "as", Type: shortcut.FlagString, Enum: []string{"user", "bot", "webhook"}, Desc: "--identity 的 lark-cli 对齐别名"}, {Name: "group", Type: shortcut.FlagString, Desc: "群 openConversationId(user/bot 群聊)"}, {Name: "chat-id", Type: shortcut.FlagString, Desc: "--group 的 lark-cli 对齐别名"}, + {Name: "user", Type: shortcut.FlagString, Desc: "单聊接收者 userId(user;自动解析 openDingTalkId)"}, {Name: "open-dingtalk-id", Type: shortcut.FlagString, Desc: "单聊接收者 openDingTalkId(user)"}, {Name: "users", Type: shortcut.FlagStringSlice, Desc: "批量单聊接收者 userId(bot)"}, {Name: "open-dingtalk-ids", Type: shortcut.FlagStringSlice, Desc: "批量单聊接收者 openDingTalkId(bot)"}, - {Name: "robot-code", Type: shortcut.FlagString, Desc: "机器人 Code(bot 必填)"}, - {Name: "webhook-token", Type: shortcut.FlagString, Desc: "自定义机器人 Webhook token(webhook 必填)"}, + {Name: "robot-code", Type: shortcut.FlagString, Desc: "机器人 Code(identity=bot 时使用)"}, + {Name: "webhook-token", Type: shortcut.FlagString, Desc: "自定义机器人 Webhook token(identity=webhook 时使用)"}, {Name: "title", Type: shortcut.FlagString, Desc: "消息标题(不传则从正文生成)"}, {Name: "text", Type: shortcut.FlagString, Desc: "纯文本正文"}, {Name: "markdown", Type: shortcut.FlagString, Desc: "Markdown 正文"}, + {Name: "msg-type", Type: shortcut.FlagString, Enum: []string{"text", "markdown", "image", "file", "audio", "video"}, Desc: "内容类型;省略时根据正文、--media-id 或 --file 自动推断"}, + {Name: "media-id", Type: shortcut.FlagString, Desc: "已有图片 mediaId(仅 user 的 image)"}, + {Name: "file", Type: shortcut.FlagString, Desc: "工作目录内安全相对文件路径(仅 user 的 file/audio/video)"}, + {Name: "file-path", Type: shortcut.FlagString, Desc: "--file 的兼容别名"}, {Name: "uuid", Type: shortcut.FlagString, Desc: "幂等键(仅 user)"}, {Name: "idempotency-key", Type: shortcut.FlagString, Desc: "--uuid 的 lark-cli 对齐别名(仅 user)"}, {Name: "at-open-dingtalk-ids", Type: shortcut.FlagStringSlice, Desc: "@ 的 openDingTalkId(user/bot 群聊)"}, @@ -47,19 +57,21 @@ var MessagesSend = shortcut.Shortcut{ shortcut.AIMessageTagFlag(), }, Constraints: []shortcut.Constraint{ - {Kind: shortcut.ConstraintAtLeastOne, Flags: []string{"text", "markdown"}}, - {Kind: shortcut.ConstraintMutuallyExclusive, Flags: []string{"text", "markdown"}}, + {Kind: shortcut.ConstraintAtLeastOne, Flags: []string{"text", "markdown", "media-id", "file", "file-path"}}, + {Kind: shortcut.ConstraintMutuallyExclusive, Flags: []string{"text", "markdown", "media-id", "file", "file-path"}}, {Kind: shortcut.ConstraintMutuallyExclusive, Flags: []string{"identity", "as"}}, {Kind: shortcut.ConstraintMutuallyExclusive, Flags: []string{"group", "chat-id"}}, + {Kind: shortcut.ConstraintMutuallyExclusive, Flags: []string{"user", "open-dingtalk-id"}}, {Kind: shortcut.ConstraintMutuallyExclusive, Flags: []string{"uuid", "idempotency-key"}}, { Kind: shortcut.ConstraintCustom, - Flags: []string{"identity", "as", "group", "chat-id", "open-dingtalk-id", "users", "open-dingtalk-ids", "robot-code", "webhook-token", "uuid", "idempotency-key"}, - Description: "user 必须指定 group/open-dingtalk-id 之一;bot 必须指定 robot-code 和群聊/批量单聊目标之一;webhook 必须指定 webhook-token;幂等键仅 user 支持", + Flags: []string{"identity"}, + Description: "身份相关目标、内容和凭据不能为空,完整能力矩阵见命令说明", }, }, Tips: []string{ `dws chat +messages-send --as user --chat-id --markdown "## 周报" --idempotency-key `, + `dws chat +messages-send --as user --user --msg-type file --file ./report.pdf --idempotency-key `, `dws chat +messages-send --as bot --robot-code --users userId1,userId2 --text "请提交周报"`, }, Validate: validateMessagesSend, @@ -69,16 +81,28 @@ var MessagesSend = shortcut.Shortcut{ func validateMessagesSend(rt *shortcut.RuntimeContext) error { identity := messagesSendIdentity(rt) group := rt.StrFirst("chat-id", "group") + userID := rt.Str("user") openID := rt.Str("open-dingtalk-id") users := uniqueShortcutStrings(rt.StrSlice("users")) openIDs := uniqueShortcutStrings(rt.StrSlice("open-dingtalk-ids")) atOpenIDs := uniqueShortcutStrings(rt.StrSlice("at-open-dingtalk-ids")) atUserIDs := uniqueShortcutStrings(rt.StrSlice("at-user-ids")) atMobiles := uniqueShortcutStrings(rt.StrSlice("at-mobiles")) + contentType, err := messagesSendContentType(rt) + if err != nil { + return err + } + if contentType == "text" && rt.Str("markdown") != "" { + return apperrors.NewValidation("--msg-type text 必须与 --text 一起使用") + } + if contentType == "markdown" && rt.Str("text") != "" { + return apperrors.NewValidation("--msg-type markdown 必须与 --markdown 一起使用") + } switch identity { case "user": - if (group == "") == (openID == "") { - return apperrors.NewValidation("--identity user 时 --group 与 --open-dingtalk-id 必须且只能指定一个") + targetCount := nonEmptyStringCount(group, userID, openID) + if targetCount != 1 { + return apperrors.NewValidation("--identity user 时 --group、--user、--open-dingtalk-id 必须且只能指定一个") } if len(users) > 0 || len(openIDs) > 0 || rt.Str("robot-code") != "" || rt.Str("webhook-token") != "" { return apperrors.NewValidation("--identity user 不接受 bot/webhook 凭据或批量目标") @@ -86,9 +110,12 @@ func validateMessagesSend(rt *shortcut.RuntimeContext) error { if len(atUserIDs) > 0 || len(atMobiles) > 0 { return apperrors.NewValidation("--identity user 只接受 --at-open-dingtalk-ids") } - if openID != "" && (len(atOpenIDs) > 0 || rt.Bool("at-all")) { + if (userID != "" || openID != "") && (len(atOpenIDs) > 0 || rt.Bool("at-all")) { return apperrors.NewValidation("user 单聊不接受 @ 参数;@ 只适用于群聊") } + if contentType != "text" && contentType != "markdown" && (len(atOpenIDs) > 0 || rt.Bool("at-all")) { + return apperrors.NewValidation("user image/file/audio/video 当前不接受 @ 参数") + } case "bot": if rt.Str("robot-code") == "" { return apperrors.NewValidation("--identity bot 必须指定 --robot-code") @@ -97,8 +124,8 @@ func validateMessagesSend(rt *shortcut.RuntimeContext) error { if (group != "") == hasDirect { return apperrors.NewValidation("--identity bot 时 --group 与批量单聊目标必须且只能指定一类") } - if openID != "" || rt.Str("webhook-token") != "" { - return apperrors.NewValidation("--identity bot 不接受 --open-dingtalk-id 或 --webhook-token") + if userID != "" || openID != "" || rt.Str("webhook-token") != "" { + return apperrors.NewValidation("--identity bot 不接受 --user、--open-dingtalk-id 或 --webhook-token") } if len(atMobiles) > 0 { return apperrors.NewValidation("--identity bot 不接受 --at-mobiles") @@ -109,11 +136,14 @@ func validateMessagesSend(rt *shortcut.RuntimeContext) error { if messagesSendIdempotencyKey(rt) != "" { return apperrors.NewValidation("--uuid 当前仅 user 身份的下层支持") } + if contentType != "text" && contentType != "markdown" { + return apperrors.NewValidation("--identity bot 当前下层只支持 text/markdown") + } case "webhook": if rt.Str("webhook-token") == "" { return apperrors.NewValidation("--identity webhook 必须指定 --webhook-token") } - if group != "" || openID != "" || len(users) > 0 || len(openIDs) > 0 || rt.Str("robot-code") != "" { + if group != "" || userID != "" || openID != "" || len(users) > 0 || len(openIDs) > 0 || rt.Str("robot-code") != "" { return apperrors.NewValidation("--identity webhook 的目标由 token 所在群决定,不接受其他目标或 bot Code") } if len(atOpenIDs) > 0 { @@ -122,6 +152,9 @@ func validateMessagesSend(rt *shortcut.RuntimeContext) error { if messagesSendIdempotencyKey(rt) != "" { return apperrors.NewValidation("--uuid 当前仅 user 身份的下层支持") } + if contentType != "text" && contentType != "markdown" { + return apperrors.NewValidation("--identity webhook 当前下层只支持 text/markdown") + } } return nil } @@ -135,12 +168,35 @@ func executeMessagesSend(rt *shortcut.RuntimeContext) error { } switch identity { case "user": + contentType, err := messagesSendContentType(rt) + if err != nil { + return err + } + group, openID, err := messagesSendUserTarget(rt) + if err != nil { + return err + } + if contentType == "image" { + content, _ := json.Marshal(map[string]string{"mediaId": rt.Str("media-id")}) + params := rt.AddAIMessageTag(map[string]any{ + "msgType": "image", + "content": string(content), + }) + addMessagesSendUserTarget(params, group, openID) + if value := messagesSendIdempotencyKey(rt); value != "" { + params["uuid"] = value + } + return executeUnifiedMessageWrite(rt, "chat", "send_personal_message", params) + } + if contentType == "file" || contentType == "audio" || contentType == "video" { + return executeMessagesSendUserFile(rt, group, openID, contentType) + } content, _ := json.Marshal(map[string]string{"title": title, "text": body}) params := rt.AddAIMessageTag(map[string]any{ "msgType": "markdown", "content": string(content), }) - if group := rt.StrFirst("chat-id", "group"); group != "" { + if group != "" { params["openConversationId"] = group if values := uniqueShortcutStrings(rt.StrSlice("at-open-dingtalk-ids")); len(values) > 0 { params["atOpenDingTalkIds"] = values @@ -149,7 +205,7 @@ func executeMessagesSend(rt *shortcut.RuntimeContext) error { params["atAll"] = true } } else { - params["receiverOpenDingTalkId"] = rt.Str("open-dingtalk-id") + params["receiverOpenDingTalkId"] = openID } if value := messagesSendIdempotencyKey(rt); value != "" { params["uuid"] = value @@ -241,6 +297,157 @@ func messagesSendBody(rt *shortcut.RuntimeContext) string { return rt.StrFirst("markdown", "text") } +func messagesSendContentType(rt *shortcut.RuntimeContext) (string, error) { + contentType := strings.ToLower(rt.Str("msg-type")) + switch { + case rt.Str("media-id") != "": + if contentType != "" && contentType != "image" { + return "", apperrors.NewValidation("--media-id 只支持 --msg-type image") + } + return "image", nil + case rt.StrFirst("file", "file-path") != "": + if contentType == "" { + return "file", nil + } + if contentType != "file" && contentType != "audio" && contentType != "video" { + return "", apperrors.NewValidation("--file 只支持 --msg-type file/audio/video") + } + return contentType, nil + default: + if contentType == "" { + if rt.Str("markdown") != "" { + return "markdown", nil + } + return "text", nil + } + if contentType != "text" && contentType != "markdown" { + return "", apperrors.NewValidation(fmt.Sprintf("--msg-type %s 需要匹配的 --media-id 或 --file", contentType)) + } + return contentType, nil + } +} + +func messagesSendUserTarget(rt *shortcut.RuntimeContext) (group, openID string, err error) { + group = rt.StrFirst("chat-id", "group") + openID = rt.Str("open-dingtalk-id") + if openID != "" || group != "" { + return group, openID, nil + } + userID := rt.Str("user") + data, err := rt.CallMCPData("contact", "get_user_info_by_user_ids", map[string]any{ + "user_id_list": []string{userID}, + }) + if err != nil { + return "", "", fmt.Errorf("把 --user 解析为 openDingTalkId 失败: %w", err) + } + openID = findOpenDingTalkID(data) + if openID == "" { + return "", "", apperrors.NewValidation("无法把 --user 解析为 openDingTalkId;请改传 --open-dingtalk-id") + } + return "", openID, nil +} + +func executeMessagesSendUserFile( + rt *shortcut.RuntimeContext, + group, openID, requestedType string, +) error { + rawPath := rt.StrFirst("file", "file-path") + safePath, err := apperrors.SafeInputPath(rawPath) + if err != nil { + return err + } + meta, err := helpers.BuildConversationLocalFileMeta(safePath, "", "") + if err != nil { + return err + } + targetArgs := map[string]any{} + addMessagesSendUserTarget(targetArgs, group, openID) + idempotencyKey := messagesSendIdempotencyKey(rt) + if rt.DryRun() { + return rt.Output(map[string]any{ + "dry_run": true, + "executed": false, + "preview_kind": "plan", + "actionCount": 2, + "failedCount": 0, + "actions": []map[string]any{ + { + "identity": "user", + "tool": "init/commit_conversation_file_upload", + "file": map[string]any{ + "path": rawPath, + "name": meta.FileName, + "sizeBytes": meta.FileSize, + }, + }, + { + "identity": "user", + "tool": "send_personal_message", + "requestedMessageType": requestedType, + "effectiveMessageType": "file", + "target": targetArgs, + }, + }, + }) + } + uploadContext, cancelUpload := context.WithTimeout( + rt.Command().Context(), messagesSendFileUploadTimeout) + defer cancelUpload() + commitText, err := helpers.UploadConversationLocalFile( + uploadContext, targetArgs, meta, idempotencyKey) + if err != nil { + return err + } + dentryID, spaceID, err := helpers.ParseConversationFileSendIDs(commitText) + if err != nil { + return err + } + content, _ := helpers.BuildConversationFileContent(dentryID, spaceID, meta) + params := rt.AddAIMessageTag(map[string]any{ + "msgType": "file", + "content": content, + }) + addMessagesSendUserTarget(params, group, openID) + if idempotencyKey != "" { + params["uuid"] = idempotencyKey + } + data, err := rt.CallMCPWriteData("chat", "send_personal_message", params) + if err != nil { + return err + } + return rt.Output(map[string]any{ + "ok": true, + "identity": "user", + "tool": "send_personal_message", + "requestedMessageType": requestedType, + "effectiveMessageType": "file", + "file": map[string]any{ + "path": rawPath, + "name": meta.FileName, + "sizeBytes": meta.FileSize, + }, + "result": data, + }) +} + +func addMessagesSendUserTarget(params map[string]any, group, openID string) { + if group != "" { + params["openConversationId"] = group + return + } + params["receiverOpenDingTalkId"] = openID +} + +func nonEmptyStringCount(values ...string) int { + count := 0 + for _, value := range values { + if strings.TrimSpace(value) != "" { + count++ + } + } + return count +} + func messagesSendIdempotencyKey(rt *shortcut.RuntimeContext) string { return rt.StrFirst("idempotency-key", "uuid") } diff --git a/internal/shortcut/chatmsg/chatmsg.go b/internal/shortcut/chatmsg/chatmsg.go index 62e0b2bf..8cc10355 100644 --- a/internal/shortcut/chatmsg/chatmsg.go +++ b/internal/shortcut/chatmsg/chatmsg.go @@ -207,21 +207,25 @@ func firstMessageValue(m map[string]any, keys ...string) any { return nil } -// Resources extracts actionable media references from both structured message -// fields and the textual mediaId notation returned by older DingTalk message -// APIs. Every reference publishes the exact Shortcut arguments already known -// from the message, plus ready=false and missing fields when a follow-up lookup -// is still required. This shared shape is used by list, search, mget, quoted -// messages and thread replies. +// Resources extracts actionable media and drive-file references from both +// structured message fields and the textual mediaId/fileId notation returned +// by older DingTalk message APIs. Every reference publishes the exact Shortcut +// arguments already known from the message, plus ready=false and missing fields +// when a follow-up lookup is still required. This shared shape is used by list, +// search, mget, quoted messages and thread replies. func Resources(m map[string]any) []map[string]any { if m == nil { return nil } - ids := make([]string, 0) - collectMediaIDs(m, &ids) - ids = uniqueMediaIDs(ids) - sort.Strings(ids) - if len(ids) == 0 { + mediaIDs := make([]string, 0) + collectResourceIDs(m, "mediaid", mediaIDTextRE, &mediaIDs) + mediaIDs = uniqueResourceIDs(mediaIDs) + sort.Strings(mediaIDs) + fileIDs := make([]string, 0) + collectResourceIDs(m, "fileid", fileIDTextRE, &fileIDs) + fileIDs = uniqueResourceIDs(fileIDs) + sort.Strings(fileIDs) + if len(mediaIDs) == 0 && len(fileIDs) == 0 { return nil } @@ -234,8 +238,8 @@ func Resources(m map[string]any) []map[string]any { conversationID = "" } - out := make([]map[string]any, 0, len(ids)) - for _, id := range ids { + out := make([]map[string]any, 0, len(mediaIDs)+len(fileIDs)) + for _, id := range mediaIDs { arguments := map[string]any{ "type": "mediaId", "resource-id": id, @@ -262,36 +266,53 @@ func Resources(m map[string]any) []map[string]any { }, }) } + for _, id := range fileIDs { + out = append(out, map[string]any{ + "type": "fileId", + "resourceId": id, + "download": map[string]any{ + "shortcut": "+messages-resource-download", + "arguments": map[string]any{ + "type": "fileId", + "resource-id": id, + }, + "ready": true, + "missing": []string{}, + }, + }) + } return out } var mediaIDTextRE = regexp.MustCompile(`(?i)media[_-]?id\s*[:=]\s*["']?([^"'\s)\]}>,]+)`) +var fileIDTextRE = regexp.MustCompile(`(?i)\bfile[_-]?id\s*[:=]\s*["']?([^"'\s)\]}>,]+)`) -func collectMediaIDs(value any, out *[]string) { +func collectResourceIDs(value any, targetKey string, textPattern *regexp.Regexp, out *[]string) { switch typed := value.(type) { case map[string]any: resourceType := strings.TrimSpace(fmt.Sprint(firstMessageValue(typed, "resourceType", "resource_type"))) for key, child := range typed { normalizedKey := strings.ToLower(strings.NewReplacer("_", "", "-", "").Replace(key)) - if normalizedKey == "mediaid" || (normalizedKey == "resourceid" && strings.EqualFold(resourceType, "mediaId")) { - if id := mediaIDScalar(child); id != "" { + if normalizedKey == targetKey || + (normalizedKey == "resourceid" && strings.EqualFold(resourceType, targetKey)) { + if id := resourceIDScalar(child); id != "" { *out = append(*out, id) } } - collectMediaIDs(child, out) + collectResourceIDs(child, targetKey, textPattern, out) } case []any: for _, child := range typed { - collectMediaIDs(child, out) + collectResourceIDs(child, targetKey, textPattern, out) } case []map[string]any: for _, child := range typed { - collectMediaIDs(child, out) + collectResourceIDs(child, targetKey, textPattern, out) } case string: - for _, match := range mediaIDTextRE.FindAllStringSubmatch(typed, -1) { + for _, match := range textPattern.FindAllStringSubmatch(typed, -1) { if len(match) > 1 { - if id := mediaIDScalar(match[1]); id != "" { + if id := resourceIDScalar(match[1]); id != "" { *out = append(*out, id) } } @@ -300,13 +321,13 @@ func collectMediaIDs(value any, out *[]string) { if strings.HasPrefix(trimmed, "{") || strings.HasPrefix(trimmed, "[") { var decoded any if json.Unmarshal([]byte(trimmed), &decoded) == nil { - collectMediaIDs(decoded, out) + collectResourceIDs(decoded, targetKey, textPattern, out) } } } } -func mediaIDScalar(value any) string { +func resourceIDScalar(value any) string { text, ok := value.(string) if !ok { return "" @@ -314,7 +335,7 @@ func mediaIDScalar(value any) string { return strings.Trim(strings.TrimSpace(text), `"'`) } -func uniqueMediaIDs(values []string) []string { +func uniqueResourceIDs(values []string) []string { out := make([]string, 0, len(values)) seen := map[string]bool{} for _, value := range values { diff --git a/internal/shortcut/chatmsg/coverage_completion_test.go b/internal/shortcut/chatmsg/coverage_completion_test.go index 52eda4de..ef8c8742 100644 --- a/internal/shortcut/chatmsg/coverage_completion_test.go +++ b/internal/shortcut/chatmsg/coverage_completion_test.go @@ -29,14 +29,27 @@ func TestCrossPlatformCoverageQuotedResourcesAndScalarVariants(t *testing.T) { resources := Resources(map[string]any{ "attachments": []map[string]any{ {"resourceType": "mediaId", "resourceId": "@file-a"}, - {"mediaId": 42}, + {"resourceType": "fileId", "resourceId": "drive-file"}, + {"mediaId": 42, "fileId": 42}, }, + "content": `[文件] report.txt fileId: drive-file`, }) - if len(resources) != 1 || resources[0]["resourceId"] != "@file-a" { + if len(resources) != 2 || + resources[0]["resourceId"] != "@file-a" || + resources[1]["resourceId"] != "drive-file" || + resources[1]["type"] != "fileId" { t.Fatalf("resources = %#v", resources) } - if got := mediaIDScalar(42); got != "" { - t.Fatalf("non-string media ID = %q", got) + fileDownload := resources[1]["download"].(map[string]any) + fileArguments := fileDownload["arguments"].(map[string]any) + if fileDownload["ready"] != true || + fileDownload["shortcut"] != "+messages-resource-download" || + fileArguments["type"] != "fileId" || + fileArguments["resource-id"] != "drive-file" { + t.Fatalf("file download = %#v", fileDownload) + } + if got := resourceIDScalar(42); got != "" { + t.Fatalf("non-string resource ID = %q", got) } } diff --git a/internal/shortcut/runner.go b/internal/shortcut/runner.go index 93ab8c81..efe0daeb 100644 --- a/internal/shortcut/runner.go +++ b/internal/shortcut/runner.go @@ -172,15 +172,7 @@ func dryRunWriteError(product, tool string) error { } func looksReadTool(tool string) bool { - tool = strings.TrimSpace(strings.ToLower(tool)) - for _, prefix := range []string{ - "get_", "list_", "query_", "search_", "unread_", - } { - if strings.HasPrefix(tool, prefix) { - return true - } - } - return false + return helpers.IsReadToolName(tool) } func (rt *RuntimeContext) callMCPData(product, tool string, params map[string]any) (map[string]any, error) { diff --git a/internal/shortcut/semantic_catalog.json b/internal/shortcut/semantic_catalog.json index 6a4f9bfe..5a45ba8d 100644 --- a/internal/shortcut/semantic_catalog.json +++ b/internal/shortcut/semantic_catalog.json @@ -435,7 +435,7 @@ }, "+messages-send": { "disposition": "semantic_adapter", - "semantic_delta": "用统一 identity 参数路由 current-user、bot、webhook 文本/Markdown 发送;按身份校验目标与凭据,幂等键只在真实支持的 user 分支开放,媒体上传仍诚实留在 native leaf。", + "semantic_delta": "用统一 identity 参数路由 current-user、bot、webhook 发送;current-user 支持文本、Markdown、mediaId 图片、安全相对路径本地文件上传、userId 姓名解析与幂等键,bot/webhook 仍只暴露下层真实支持的文本/Markdown 能力。", "risk": "write", "public": true, "reviewed": true @@ -487,7 +487,7 @@ }, "+messages-mget": { "disposition": "semantic_adapter", - "semantic_delta": "按最多 50 个消息 ID 批量读取并输出稳定消息投影、reaction 与 resourceRefs;可用 --download-resources 复用 HTTPS、相对路径、无覆盖和原子落盘防护,逐资源返回下载失败 ledger。", + "semantic_delta": "按最多 50 个消息 ID 批量读取并输出稳定消息投影、reaction 与 resourceRefs;可用 --download-resources 统一下载 mediaId 与 fileId,复用 HTTPS、相对路径、无覆盖和原子落盘防护,对重复资源去重并逐资源返回下载失败 ledger。", "risk": "read", "public": true, "reviewed": true @@ -542,8 +542,8 @@ "reviewed": true }, "+messages-send-card": { - "disposition": "schema_leaf", - "semantic_delta": "创建流式卡片是一对一写入;完整卡片生命周期需由 send/update leaf 明确编排。", + "disposition": "semantic_adapter", + "semantic_delta": "既可只创建流式卡片,也可在一次调用中创建、提取 bizId、写入内容并设置流式状态;dry-run 输出两步执行计划,更新失败时保留已创建的 bizId。", "risk": "write", "public": true, "reviewed": true @@ -564,7 +564,7 @@ }, "+messages-resource-download": { "disposition": "primary_smart", - "semantic_delta": "把临时资源 URL 解析、工作目录内安全路径、默认不覆盖、临时文件下载和原子发布封装为结构化单步结果。", + "semantic_delta": "统一承接消息 mediaId 与钉盘 fileId:分别复用 IM 临时资源 URL 和 drive.download_file,再通过工作目录内安全路径、默认不覆盖、临时文件下载和原子发布输出结构化结果。", "risk": "read", "public": true, "reviewed": true @@ -627,7 +627,7 @@ }, "+at-me": { "disposition": "primary_smart", - "semantic_delta": "自动构造时间窗,分页拉取跨会话 @我 消息,并保留身份、引用、reaction、resourceRefs 与完整性。", + "semantic_delta": "自动构造时间窗,分页拉取跨会话 @我 消息,并保留身份、引用、reaction、resourceRefs 与完整性;可选对资源去重后安全落盘并返回逐项失败 ledger。", "risk": "read", "public": true, "reviewed": true @@ -641,7 +641,7 @@ }, "+chat-messages": { "disposition": "primary_smart", - "semantic_delta": "统一群聊与两类单聊目标,输出稳定消息身份、引用、reaction、resourceRefs、时间边界翻页和可读正文。", + "semantic_delta": "统一群聊与两类单聊目标;省略时间时自动以当前时间向前读取最近消息,并输出稳定消息身份、引用、reaction、resourceRefs、时间边界翻页和可读正文;可选对列表内资源去重后安全落盘并返回逐项失败 ledger。", "risk": "read", "public": true, "reviewed": true @@ -677,7 +677,7 @@ }, "+search-msg": { "disposition": "primary_smart", - "semantic_delta": "统一关键词、发送者、@对象、会话、消息类型和机器人来源过滤;支持精确时间窗、page-all、50 条一组 mget 富化,并以 failure ledger 显式报告截断或富化失败。", + "semantic_delta": "统一关键词、发送者、@对象、会话、消息类型和机器人来源过滤;展开下层按会话分组的 conversationMessagesList,支持精确时间窗、page-all、50 条一组 mget 富化,可选安全下载命中消息资源,并以 failure ledger 显式报告截断、富化或下载失败。", "risk": "read", "public": true, "reviewed": true @@ -691,7 +691,7 @@ }, "+thread-replies": { "disposition": "primary_smart", - "semantic_delta": "接受消息列表直接返回的 threadId(兼容 topicId),拉取回复并输出稳定身份、引用、reaction、resourceRefs、可读正文和时间边界分页。", + "semantic_delta": "接受消息列表直接返回的 threadId(兼容 topicId),拉取回复并输出稳定身份、引用、reaction、resourceRefs、可读正文和时间边界分页;可选对回复资源去重后安全落盘并返回逐项失败 ledger。", "risk": "read", "public": true, "reviewed": true diff --git a/internal/shortcut/smart/at_me.go b/internal/shortcut/smart/at_me.go index 6f1a5d0b..bb052a05 100644 --- a/internal/shortcut/smart/at_me.go +++ b/internal/shortcut/smart/at_me.go @@ -19,6 +19,7 @@ import ( "time" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut" + chatshortcut "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut/chat" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut/chatmsg" ) @@ -53,16 +54,18 @@ var AtMe = shortcut.Shortcut{ "再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。" + "这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。", Risk: shortcut.RiskRead, - Flags: []shortcut.Flag{ + Flags: append([]shortcut.Flag{ {Name: "days", Type: shortcut.FlagInt, Desc: "回溯天数(可选,默认 7)", Default: "7", Required: false}, {Name: "limit", Type: shortcut.FlagInt, Desc: "每页返回数量(默认 50)", Default: "50"}, {Name: "cursor", Type: shortcut.FlagString, Desc: "分页游标,翻页传上次的 nextCursor", Default: "0"}, {Name: "no-reactions", Type: shortcut.FlagBool, Desc: "不输出消息 reaction(默认输出)"}, - }, + }, chatshortcut.MessageResourceDownloadFlags()...), + Constraints: chatshortcut.MessageResourceDownloadConstraints(), Tips: []string{ `dws chat +at-me`, `dws chat +at-me --days 3`, }, + Validate: chatshortcut.ValidateMessageResourceDownload, Execute: func(rt *shortcut.RuntimeContext) error { // Step 1 — look-back window [now-Nd, now] in epoch millis. days defaults // to 7; guard against non-positive overrides so the window stays sane. @@ -99,6 +102,9 @@ var AtMe = shortcut.Shortcut{ } payload := map[string]any{"messages": results} chatmsg.ApplyPagination(payload, data) + if rt.Bool("download-resources") { + payload["resourceDownloads"] = chatshortcut.DownloadMessageResources(rt, items, "") + } return rt.Output(payload) }, } diff --git a/internal/shortcut/smart/chat_messages.go b/internal/shortcut/smart/chat_messages.go index 19720339..d586f5e0 100644 --- a/internal/shortcut/smart/chat_messages.go +++ b/internal/shortcut/smart/chat_messages.go @@ -15,8 +15,10 @@ package smart import ( "strings" + "time" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut" + chatshortcut "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut/chat" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut/chatmsg" ) @@ -47,39 +49,43 @@ var ChatMessages = shortcut.Shortcut{ Description: "拉取某个会话(群聊或单聊)的消息列表并投影出发言人/文本/时间", Intent: "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;" + "群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。" + - "可选 --time 指定起始时间、--limit 指定每页条数、--direction newer/older 控制时间方向(newer 从给定时间往现在拉,older 往以前拉)。" + + "省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。" + "内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。" + "这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。", Risk: shortcut.RiskRead, - Flags: []shortcut.Flag{ + Flags: append([]shortcut.Flag{ {Name: "group", Type: shortcut.FlagString, Desc: "群会话 ID(openConversationId),与 --user 互斥"}, {Name: "conversation-id", Type: shortcut.FlagString, Desc: "--group 的别名", Hidden: true}, {Name: "id", Type: shortcut.FlagString, Desc: "--group 的别名", Hidden: true}, {Name: "user", Type: shortcut.FlagString, Desc: "单聊对方的 userId,与 --group 互斥"}, {Name: "open-dingtalk-id", Type: shortcut.FlagString, Desc: "单聊对方的 openDingTalkId,与 --group/--user 互斥"}, - {Name: "time", Type: shortcut.FlagString, Desc: "起始时间,如 \"2025-03-01 00:00:00\"(可选)"}, + {Name: "time", Type: shortcut.FlagString, Desc: "时间边界,如 \"2025-03-01 00:00:00\";省略时从当前时间向前读取最近消息"}, {Name: "limit", Type: shortcut.FlagInt, Desc: "每页拉取的消息条数(可选)"}, {Name: "size", Type: shortcut.FlagInt, Desc: "--limit 的旧版别名", Hidden: true}, - {Name: "direction", Type: shortcut.FlagString, Desc: "时间方向 newer/older(可选,newer 从给定时间往现在拉,older 往以前拉)"}, + {Name: "direction", Type: shortcut.FlagString, Enum: []string{"newer", "older"}, Desc: "时间方向 newer/older;省略时为 older,从时间边界向前读取"}, {Name: "no-reactions", Type: shortcut.FlagBool, Desc: "不输出消息 reaction(默认输出)"}, - }, - Constraints: []shortcut.Constraint{ + }, chatshortcut.MessageResourceDownloadFlags()...), + Constraints: append([]shortcut.Constraint{ {Kind: shortcut.ConstraintExactlyOne, Flags: []string{"group", "conversation-id", "id", "user", "open-dingtalk-id"}}, - }, + }, chatshortcut.MessageResourceDownloadConstraints()...), Tips: []string{ `dws chat +chat-messages --group --time "2025-03-01 00:00:00"`, `dws chat +chat-messages --user --time "2025-03-01 00:00:00" --limit 50`, `dws chat +chat-messages --group --direction older`, }, + Validate: chatshortcut.ValidateMessageResourceDownload, Execute: func(rt *shortcut.RuntimeContext) error { // Step 1 — build params and pick the right tool. Param keys // (openconversation_id / userId / time / forward / limit) match the MCP server // schema for group and direct message listing. var tool string params := map[string]any{} + fallbackConversationID := "" if rt.Changed("time") && rt.Str("time") != "" { params["time"] = rt.Str("time") + } else { + params["time"] = time.Now().Format("2006-01-02 15:04:05") } if limit := rt.IntFirst("limit", "size"); limit > 0 { params["limit"] = limit @@ -94,11 +100,14 @@ var ChatMessages = shortcut.Shortcut{ case "older": params["forward"] = false } + } else { + params["forward"] = false } if group := rt.StrFirst("group", "conversation-id", "id"); group != "" { tool = "list_conversation_message_v2" params["openconversation_id"] = group + fallbackConversationID = group } else if openID := strings.TrimSpace(rt.Str("open-dingtalk-id")); openID != "" { tool = "list_individual_chat_message" params["openDingTalkId"] = openID @@ -126,6 +135,9 @@ var ChatMessages = shortcut.Shortcut{ } direction := strings.TrimSpace(strings.ToLower(rt.Str("direction"))) chatmsg.ApplyMessagePagination(payload, data, items, direction) + if rt.Bool("download-resources") { + payload["resourceDownloads"] = chatshortcut.DownloadMessageResources(rt, items, fallbackConversationID) + } return rt.Output(payload) }, } diff --git a/internal/shortcut/smart/gap_fill_test.go b/internal/shortcut/smart/gap_fill_test.go new file mode 100644 index 00000000..dc65a0c5 --- /dev/null +++ b/internal/shortcut/smart/gap_fill_test.go @@ -0,0 +1,188 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); + +package smart + +import ( + "bytes" + "encoding/json" + "testing" + "time" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/helpers" +) + +func TestCrossPlatformCoverageMessageReadShortcutsPublishResourceDownloadPlans(t *testing.T) { + message := `{"openMessageId":"msg","openConversationId":"cid","content":"{\"mediaId\":\"@image\"}"}` + tests := []struct { + name string + tool string + response string + args []string + resultKey string + }{ + { + name: "chat messages", + tool: "chat/list_conversation_message_v2", + response: `{"result":{"messages":[` + message + `]}}`, + args: []string{"chat", "+chat-messages", "--group", "cid"}, + resultKey: "messages", + }, + { + name: "search", + tool: "im/search_messages", + response: `{"result":{"messages":[` + message + `],"hasMore":false}}`, + args: []string{"chat", "+search-msg", "--query", "x", "--no-enrich"}, + resultKey: "messages", + }, + { + name: "at me", + tool: "chat/search_at_me_message", + response: `{"result":{"conversationMessagesList":[{"openConversationId":"cid","messages":[` + message + `]}]}}`, + args: []string{"chat", "+at-me"}, + resultKey: "messages", + }, + { + name: "thread replies", + tool: "chat/list_topic_replies", + response: `{"result":{"messages":[` + message + `]}}`, + args: []string{"chat", "+thread-replies", "--group", "cid", "--thread-id", "thread"}, + resultKey: "replies", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + caller := &smartCoverageCaller{responses: map[string][]string{ + tc.tool: {tc.response}, + }} + helpers.InitDeps(caller) + root := newPlatformCoverageRoot() + var output bytes.Buffer + root.SetOut(&output) + args := append([]string{}, tc.args...) + args = append(args, "--download-resources", "--output-dir", "./downloads", "--dry-run") + root.SetArgs(args) + if err := root.Execute(); err != nil { + t.Fatal(err) + } + if caller.counts[tc.tool] != 1 { + t.Fatalf("lower calls = %#v", caller.counts) + } + var payload map[string]any + if err := json.Unmarshal(output.Bytes(), &payload); err != nil { + t.Fatalf("decode output: %v\n%s", err, output.String()) + } + if _, ok := payload[tc.resultKey]; !ok { + t.Fatalf("payload missing %s: %#v", tc.resultKey, payload) + } + ledger, _ := payload["resourceDownloads"].(map[string]any) + if ledger["dryRun"] != true || ledger["requestedCount"] != float64(1) { + t.Fatalf("resource plan = %#v", ledger) + } + }) + } +} + +func TestCrossPlatformCoverageMessageReadShortcutResourceOutputValidation(t *testing.T) { + for _, args := range [][]string{ + {"chat", "+chat-messages", "--group", "cid"}, + {"chat", "+search-msg", "--query", "x", "--no-enrich"}, + {"chat", "+at-me"}, + {"chat", "+thread-replies", "--group", "cid", "--thread-id", "thread"}, + } { + helpers.InitDeps(&smartCoverageCaller{}) + root := newPlatformCoverageRoot() + root.SetArgs(append(args, "--download-resources", "--output-dir", "../outside")) + if err := root.Execute(); err == nil { + t.Fatalf("unsafe output accepted: %v", args) + } + } +} + +func TestCrossPlatformCoverageChatMessagesDefaultsToRecentHistory(t *testing.T) { + caller := &platformCoverageCaller{} + helpers.InitDeps(caller) + root := newPlatformCoverageRoot() + before := time.Now().Add(-2 * time.Second) + root.SetArgs([]string{"chat", "+chat-messages", "--group", "cid", "--limit", "5"}) + if err := root.Execute(); err != nil { + t.Fatal(err) + } + after := time.Now().Add(2 * time.Second) + if len(caller.calls) != 1 { + t.Fatalf("calls = %#v", caller.calls) + } + call := caller.calls[0] + if call.args["forward"] != false { + t.Fatalf("default forward = %#v, want false", call.args["forward"]) + } + boundary, err := time.ParseInLocation("2006-01-02 15:04:05", call.args["time"].(string), time.Local) + if err != nil { + t.Fatal(err) + } + if boundary.Before(before) || boundary.After(after) { + t.Fatalf("default time = %s, want current boundary", boundary) + } +} + +func TestCrossPlatformCoverageChatMessagesPreservesExplicitTime(t *testing.T) { + caller := &platformCoverageCaller{} + helpers.InitDeps(caller) + root := newPlatformCoverageRoot() + root.SetArgs([]string{ + "chat", "+chat-messages", + "--group", "cid", + "--time", "2026-07-01 12:34:56", + }) + if err := root.Execute(); err != nil { + t.Fatal(err) + } + if len(caller.calls) != 1 || + caller.calls[0].args["time"] != "2026-07-01 12:34:56" { + t.Fatalf("explicit time call = %#v", caller.calls) + } +} + +func TestCrossPlatformCoverageSearchMsgFlattensRealGroupedResponse(t *testing.T) { + original := map[string]any{"openMessageId": "m1"} + items := searchMsgItems(map[string]any{ + "result": map[string]any{ + "conversationMessagesList": []any{ + "invalid-group", + map[string]any{"messages": "invalid"}, + map[string]any{ + "openConversationId": "cid", + "title": "会话", + "singleChat": true, + "messages": []any{ + "invalid-message", + original, + map[string]any{ + "openMessageId": "m2", + "openConversationId": "own-cid", + "conversationTitle": "own-title", + "singleChat": false, + }, + }, + }, + }, + }, + }) + if len(items) != 2 { + t.Fatalf("grouped items = %#v", items) + } + if items[0]["openConversationId"] != "cid" || items[0]["conversationTitle"] != "会话" || + items[0]["singleChat"] != true { + t.Fatalf("injected group context = %#v", items[0]) + } + if items[1]["openConversationId"] != "own-cid" || items[1]["conversationTitle"] != "own-title" || + items[1]["singleChat"] != false { + t.Fatalf("message context was overwritten: %#v", items[1]) + } + if _, mutated := original["openConversationId"]; mutated { + t.Fatalf("source message mutated: %#v", original) + } + if searchMsgChildMap(map[string]any{"result": "invalid"}, "result") != nil { + t.Fatal("non-map child was accepted") + } +} diff --git a/internal/shortcut/smart/search_msg.go b/internal/shortcut/smart/search_msg.go index 61bba711..f63d6613 100644 --- a/internal/shortcut/smart/search_msg.go +++ b/internal/shortcut/smart/search_msg.go @@ -21,6 +21,7 @@ import ( apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut" + chatshortcut "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut/chat" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut/chatmsg" ) @@ -37,7 +38,7 @@ var SearchMsg = shortcut.Shortcut{ Intent: "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天," + "也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。", Risk: shortcut.RiskRead, - Flags: []shortcut.Flag{ + Flags: append([]shortcut.Flag{ {Name: "query", Type: shortcut.FlagString, Desc: "搜索关键词"}, {Name: "keyword", Type: shortcut.FlagString, Desc: "--query 的别名", Hidden: true}, {Name: "group", Type: shortcut.FlagString, Desc: "单个会话 openConversationId"}, @@ -65,17 +66,22 @@ var SearchMsg = shortcut.Shortcut{ {Name: "page-limit", Type: shortcut.FlagInt, Desc: "--page-all 的最大页数(1-40)", Default: "20"}, {Name: "no-enrich", Type: shortcut.FlagBool, Desc: "不再按消息 ID 批量查询完整详情"}, {Name: "no-reactions", Type: shortcut.FlagBool, Desc: "不输出命中消息的 reaction(默认输出)"}, - }, - Constraints: []shortcut.Constraint{ + }, chatshortcut.MessageResourceDownloadFlags()...), + Constraints: append([]shortcut.Constraint{ { - Kind: shortcut.ConstraintCustom, - Flags: []string{"query", "group", "groups", "chat-id", "senders", "sender", "at-me", "is-at-me", "at-ids", "message-type", "only-robot", "conversation-type", "chat-type"}, + Kind: shortcut.ConstraintAtLeastOne, + Flags: []string{"query", "keyword", "group", "conversation-id", "id", "groups", "chat-id", "senders", "sender", "at-me", "is-at-me", "at-ids", "message-type", "only-robot", "conversation-type", "chat-type"}, Description: "至少指定一个内容、身份、会话或消息类型过滤条件", }, { Kind: shortcut.ConstraintCustom, - Flags: []string{"start", "end", "days"}, - Description: "--start 与 --end 必须同时指定;否则使用 --days 时间窗", + Flags: []string{"start"}, + Description: "需与 --end 一起传", + }, + { + Kind: shortcut.ConstraintCustom, + Flags: []string{"end"}, + Description: "需与 --start 一起传", }, {Kind: shortcut.ConstraintMutuallyExclusive, Flags: []string{"groups", "chat-id"}}, {Kind: shortcut.ConstraintMutuallyExclusive, Flags: []string{"senders", "sender"}}, @@ -83,12 +89,12 @@ var SearchMsg = shortcut.Shortcut{ {Kind: shortcut.ConstraintMutuallyExclusive, Flags: []string{"conversation-type", "chat-type"}}, {Kind: shortcut.ConstraintMutuallyExclusive, Flags: []string{"limit", "page-size"}}, {Kind: shortcut.ConstraintMutuallyExclusive, Flags: []string{"cursor", "page-token"}}, - }, + }, chatshortcut.MessageResourceDownloadConstraints()...), Tips: []string{ `dws chat +search-msg --group --query "changefree"`, `dws chat +search-msg --senders --at-me --days 3 --page-all`, }, - Validate: validateSearchMsg, + Validate: validateSearchMsgWithResources, Execute: func(rt *shortcut.RuntimeContext) error { params, err := searchMsgParams(rt) if err != nil { @@ -200,10 +206,20 @@ var SearchMsg = shortcut.Shortcut{ if hasMore && nextCursor != "" && nextCursor != "" { payload["nextCursor"] = nextCursor } + if rt.Bool("download-resources") { + payload["resourceDownloads"] = chatshortcut.DownloadMessageResources(rt, messages, "") + } return rt.Output(payload) }, } +func validateSearchMsgWithResources(rt *shortcut.RuntimeContext) error { + if err := validateSearchMsg(rt); err != nil { + return err + } + return chatshortcut.ValidateMessageResourceDownload(rt) +} + func validateSearchMsg(rt *shortcut.RuntimeContext) error { hasFilter := rt.StrFirst("query", "keyword", "group", "conversation-id", "id", "message-type", "conversation-type", "chat-type") != "" || len(rt.StrSlice("groups")) > 0 || @@ -392,6 +408,14 @@ func searchMsgItems(data map[string]any) []map[string]any { if data == nil { return nil } + for _, root := range []map[string]any{data, searchMsgChildMap(data, "result")} { + if root == nil { + continue + } + if groups, ok := root["conversationMessagesList"].([]any); ok { + return searchMsgFlattenGroups(groups) + } + } keys := []string{"list", "messages", "messageList", "items", "data", "records", "result"} for _, key := range keys { if arr, ok := data[key].([]any); ok { @@ -408,6 +432,53 @@ func searchMsgItems(data map[string]any) []map[string]any { return nil } +func searchMsgChildMap(data map[string]any, key string) map[string]any { + if value, ok := data[key].(map[string]any); ok { + return value + } + return nil +} + +func searchMsgFlattenGroups(groups []any) []map[string]any { + out := make([]map[string]any, 0) + for _, rawGroup := range groups { + group, ok := rawGroup.(map[string]any) + if !ok { + continue + } + messages, ok := group["messages"].([]any) + if !ok { + continue + } + conversationID := strings.TrimSpace(fmt.Sprint(group["openConversationId"])) + conversationTitle := strings.TrimSpace(fmt.Sprint(group["title"])) + singleChat, hasSingleChat := group["singleChat"] + for _, rawMessage := range messages { + message, ok := rawMessage.(map[string]any) + if !ok { + continue + } + item := make(map[string]any, len(message)+3) + for key, value := range message { + item[key] = value + } + if _, exists := item["openConversationId"]; !exists && + conversationID != "" && conversationID != "" { + item["openConversationId"] = conversationID + } + if _, exists := item["conversationTitle"]; !exists && + conversationTitle != "" && conversationTitle != "" { + item["conversationTitle"] = conversationTitle + } + if _, exists := item["singleChat"]; !exists && hasSingleChat { + item["singleChat"] = singleChat + } + out = append(out, item) + } + } + return out +} + func searchMsgToMaps(arr []any) []map[string]any { out := make([]map[string]any, 0, len(arr)) for _, it := range arr { diff --git a/internal/shortcut/smart/thread_replies.go b/internal/shortcut/smart/thread_replies.go index 5d387c1f..c9c7ccc7 100644 --- a/internal/shortcut/smart/thread_replies.go +++ b/internal/shortcut/smart/thread_replies.go @@ -15,6 +15,7 @@ package smart import ( "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut" + chatshortcut "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut/chat" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut/chatmsg" ) @@ -43,25 +44,26 @@ var ThreadReplies = shortcut.Shortcut{ "可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。" + "这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。", Risk: shortcut.RiskRead, - Flags: []shortcut.Flag{ + Flags: append([]shortcut.Flag{ {Name: "group", Type: shortcut.FlagString, Desc: "群会话 ID(openConversationId,必填)", Required: true}, {Name: "thread-id", Type: shortcut.FlagString, Desc: "话题/线程 ID(可直接使用消息列表返回的 threadId)"}, {Name: "topic-id", Type: shortcut.FlagString, Desc: "--thread-id 的兼容别名"}, {Name: "time", Type: shortcut.FlagString, Desc: "起始时间,如 \"2025-03-01 00:00:00\"(可选)"}, {Name: "limit", Type: shortcut.FlagInt, Desc: "每页拉取的回复条数(可选)"}, {Name: "no-reactions", Type: shortcut.FlagBool, Desc: "不输出回复 reaction(默认输出)"}, - }, - Constraints: []shortcut.Constraint{ + }, chatshortcut.MessageResourceDownloadFlags()...), + Constraints: append([]shortcut.Constraint{ { Kind: shortcut.ConstraintExactlyOne, Flags: []string{"thread-id", "topic-id"}, Description: "--thread-id 与兼容参数 --topic-id 必须且只能指定一个", }, - }, + }, chatshortcut.MessageResourceDownloadConstraints()...), Tips: []string{ `dws chat +thread-replies --group --thread-id `, `dws chat +thread-replies --group --thread-id --time "2025-03-01 00:00:00" --limit 20`, }, + Validate: chatshortcut.ValidateMessageResourceDownload, Execute: func(rt *shortcut.RuntimeContext) error { // Step 1 — fetch the topic replies. Param keys (openconversationId / // topicId / startTime / pageSize) are copied verbatim from chat.go's @@ -96,6 +98,9 @@ var ThreadReplies = shortcut.Shortcut{ "count": len(results), } chatmsg.ApplyMessagePagination(payload, data, items, "older") + if rt.Bool("download-resources") { + payload["resourceDownloads"] = chatshortcut.DownloadMessageResources(rt, items, rt.Str("group")) + } return rt.Output(payload) }, }