From 2b48f27a4b229944641f532fd017b86cfe5e4fc1 Mon Sep 17 00:00:00 2001 From: Dennis Date: Wed, 29 Jul 2026 23:35:52 +0800 Subject: [PATCH] fix(im): harden shortcut review follow-ups --- internal/cli/schema_agent_metadata/chat.json | 56 ++--- internal/cli/schema_agent_metadata/index.json | 2 +- internal/cli/schema_agent_metadata_audit.json | 2 +- internal/cli/schema_catalog/catalog.json | 8 +- internal/cli/schema_catalog/tools/chat.json | 196 ++++++++++++++---- internal/cli/schema_hints/metadata/chat.json | 8 +- internal/cli/schema_hints/selection/chat.json | 4 +- internal/shortcut/chat/chat_message.go | 36 ++-- .../chat/chat_message_coverage_test.go | 33 +++ internal/shortcut/chat/gap_fill_test.go | 15 +- internal/shortcut/chat/unified_send.go | 5 +- internal/shortcut/runner.go | 2 +- internal/shortcut/shortcut_test.go | 108 +--------- internal/shortcut/smart/gap_fill_test.go | 4 +- internal/shortcut/types.go | 37 ---- skills/mono/references/intent-guide.md | 6 + skills/multi/dingtalk-chat/SKILL.md | 4 +- .../dws-shared/references/intent-guide.md | 6 + 18 files changed, 290 insertions(+), 242 deletions(-) diff --git a/internal/cli/schema_agent_metadata/chat.json b/internal/cli/schema_agent_metadata/chat.json index b9dd2e3d..a8767750 100644 --- a/internal/cli/schema_agent_metadata/chat.json +++ b/internal/cli/schema_agent_metadata/chat.json @@ -10380,14 +10380,14 @@ "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.", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, explicit userId/openDingTalkId routing, 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." + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, explicit userId/openDingTalkId routing, two-action dry-run, bizId extraction, and partial-failure reporting." } ] }, @@ -10396,14 +10396,14 @@ "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "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." + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property." } ] }, @@ -10414,7 +10414,7 @@ "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.", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, explicit userId/openDingTalkId routing, two-action dry-run, bizId extraction, and partial-failure reporting.", "candidates": [ { "value": [ @@ -10423,7 +10423,7 @@ "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." + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, explicit userId/openDingTalkId routing, two-action dry-run, bizId extraction, and partial-failure reporting." } ] }, @@ -10432,14 +10432,14 @@ "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "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." + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property." } ] }, @@ -10448,14 +10448,14 @@ "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "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." + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property." } ] }, @@ -10467,7 +10467,7 @@ "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.", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, explicit userId/openDingTalkId routing, two-action dry-run, bizId extraction, and partial-failure reporting.", "candidates": [ { "value": [ @@ -10477,7 +10477,7 @@ "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." + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, explicit userId/openDingTalkId routing, two-action dry-run, bizId extraction, and partial-failure reporting." } ] }, @@ -10486,14 +10486,14 @@ "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "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." + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property." } ] }, @@ -10502,14 +10502,14 @@ "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "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." + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property." } ] }, @@ -10518,14 +10518,14 @@ "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "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." + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property." } ] }, @@ -10550,21 +10550,21 @@ "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "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." + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property." }, { "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." + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, explicit userId/openDingTalkId routing, two-action dry-run, bizId extraction, and partial-failure reporting." } ] }, @@ -10573,34 +10573,34 @@ "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "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." + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property." } ] }, "use_when": { "value": [ - "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId/openDingTalkId 二选一。单聊目标会统一解析后以 receiverOpenDingTalkId 传给下层;userId 包括在 --dry-run 时也会先做只读通讯录解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。" + "当你要发送一张流式卡片消息时使用;群 openConversationId、单聊 userId、单聊 openDingTalkId 严格三选一,分别使用 --group、--receiver、--receiver-open-dingtalk-id。--receiver 始终按 userId 做只读通讯录解析,即使值以 D/d 开头也不会猜成 openDingTalkId;已有 openDingTalkId 时必须用显式参数直传。userId 包括在 --dry-run 时也会先解析。只传目标时创建卡片并返回 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.", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, explicit userId/openDingTalkId routing, two-action dry-run, bizId extraction, and partial-failure reporting.", "candidates": [ { "value": [ - "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId/openDingTalkId 二选一。单聊目标会统一解析后以 receiverOpenDingTalkId 传给下层;userId 包括在 --dry-run 时也会先做只读通讯录解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。" + "当你要发送一张流式卡片消息时使用;群 openConversationId、单聊 userId、单聊 openDingTalkId 严格三选一,分别使用 --group、--receiver、--receiver-open-dingtalk-id。--receiver 始终按 userId 做只读通讯录解析,即使值以 D/d 开头也不会猜成 openDingTalkId;已有 openDingTalkId 时必须用显式参数直传。userId 包括在 --dry-run 时也会先解析。只传目标时创建卡片并返回 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." + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, explicit userId/openDingTalkId routing, two-action dry-run, bizId extraction, and partial-failure reporting." } ] } @@ -10618,7 +10618,7 @@ "internal/cli/schema_hints/selection/chat.json" ], "use_when": [ - "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId/openDingTalkId 二选一。单聊目标会统一解析后以 receiverOpenDingTalkId 传给下层;userId 包括在 --dry-run 时也会先做只读通讯录解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。" + "当你要发送一张流式卡片消息时使用;群 openConversationId、单聊 userId、单聊 openDingTalkId 严格三选一,分别使用 --group、--receiver、--receiver-open-dingtalk-id。--receiver 始终按 userId 做只读通讯录解析,即使值以 D/d 开头也不会猜成 openDingTalkId;已有 openDingTalkId 时必须用显式参数直传。userId 包括在 --dry-run 时也会先解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。" ] }, "chat +messages-update-card": { diff --git a/internal/cli/schema_agent_metadata/index.json b/internal/cli/schema_agent_metadata/index.json index 777e4554..fca0f4cc 100644 --- a/internal/cli/schema_agent_metadata/index.json +++ b/internal/cli/schema_agent_metadata/index.json @@ -1,6 +1,6 @@ { "version": 1, - "source_hash": "sha256:f48d04b4c2f9494460cba8d38ef6b6b8dd5a0172450f6f06123f1b696aaef497", + "source_hash": "sha256:f7afa1d0abaa0c3d4ead4c6262948b7565787abb0860bb724276d1d3fe52178b", "surface_hash": "sha256:60eee8e2f37d6d9d60689efce85082798eb9ad38b7ba7c0b471c3de676a85a16", "coverage": { "surface_products": 26, diff --git a/internal/cli/schema_agent_metadata_audit.json b/internal/cli/schema_agent_metadata_audit.json index 52edde8f..c0e5ff6e 100644 --- a/internal/cli/schema_agent_metadata_audit.json +++ b/internal/cli/schema_agent_metadata_audit.json @@ -1,6 +1,6 @@ { "version": 1, - "source_hash": "sha256:f48d04b4c2f9494460cba8d38ef6b6b8dd5a0172450f6f06123f1b696aaef497", + "source_hash": "sha256:f7afa1d0abaa0c3d4ead4c6262948b7565787abb0860bb724276d1d3fe52178b", "surface_hash": "sha256:60eee8e2f37d6d9d60689efce85082798eb9ad38b7ba7c0b471c3de676a85a16", "source_files": 160, "hint_files": 54, diff --git a/internal/cli/schema_catalog/catalog.json b/internal/cli/schema_catalog/catalog.json index 783287e6..60f9e4aa 100644 --- a/internal/cli/schema_catalog/catalog.json +++ b/internal/cli/schema_catalog/catalog.json @@ -1,12 +1,12 @@ { "version": 1, "surface_hash": "sha256:60eee8e2f37d6d9d60689efce85082798eb9ad38b7ba7c0b471c3de676a85a16", - "source_hash": "sha256:46e9ac44ad12175deac0891886039e6a0a2436208b29b6393fd39452ba285a25", + "source_hash": "sha256:8fcebd5100f2727898290118b2b381cc3edc325bc1c8526e61036da5482e025b", "catalog": { "agent_metadata": { "products_with_metadata": 26, "source": "embedded-skill-metadata", - "source_hash": "sha256:f48d04b4c2f9494460cba8d38ef6b6b8dd5a0172450f6f06123f1b696aaef497", + "source_hash": "sha256:f7afa1d0abaa0c3d4ead4c6262948b7565787abb0860bb724276d1d3fe52178b", "surface_hash": "sha256:60eee8e2f37d6d9d60689efce85082798eb9ad38b7ba7c0b471c3de676a85a16", "surface_products": 26, "surface_tools": 845, @@ -10840,7 +10840,7 @@ "cli_name": "+messages-send-card", "cli_path": "chat +messages-send-card", "confirmation": "user_required", - "description": "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId/openDingTalkId 二选一。单聊目标会统一解析后以 receiverOpenDingTalkId 传给下层;userId 包括在 --dry-run 时也会先做只读通讯录解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。\n\n参数约束:\n - --group、--receiver 必须且只能指定一个\n - --flow-status 必须在 1-5 之间,且显式指定时必须同时提供 --content", + "description": "当你要发送一张流式卡片消息时使用;群 openConversationId、单聊 userId、单聊 openDingTalkId 严格三选一,分别使用 --group、--receiver、--receiver-open-dingtalk-id。--receiver 始终按 userId 做只读通讯录解析,即使值以 D/d 开头也不会猜成 openDingTalkId;已有 openDingTalkId 时必须用显式参数直传。userId 包括在 --dry-run 时也会先解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。\n\n参数约束:\n - --group、--receiver、--receiver-open-dingtalk-id 必须且只能指定一个\n - --flow-status 必须在 1-5 之间,且显式指定时必须同时提供 --content", "effect": "write", "idempotency": "unknown", "interface_mode": "composite", @@ -10851,7 +10851,7 @@ "risk": "medium", "title": "创建流式卡片,可在同一次调用中写入内容并结束", "use_when": [ - "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId/openDingTalkId 二选一。单聊目标会统一解析后以 receiverOpenDingTalkId 传给下层;userId 包括在 --dry-run 时也会先做只读通讯录解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。" + "当你要发送一张流式卡片消息时使用;群 openConversationId、单聊 userId、单聊 openDingTalkId 严格三选一,分别使用 --group、--receiver、--receiver-open-dingtalk-id。--receiver 始终按 userId 做只读通讯录解析,即使值以 D/d 开头也不会猜成 openDingTalkId;已有 openDingTalkId 时必须用显式参数直传。userId 包括在 --dry-run 时也会先解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。" ] }, { diff --git a/internal/cli/schema_catalog/tools/chat.json b/internal/cli/schema_catalog/tools/chat.json index 25794c6c..40716f99 100644 --- a/internal/cli/schema_catalog/tools/chat.json +++ b/internal/cli/schema_catalog/tools/chat.json @@ -77349,17 +77349,19 @@ "mutually_exclusive": [ [ "group", - "receiver" + "receiver", + "receiver-open-dingtalk-id" ] ], "require_one_of": [ [ "group", - "receiver" + "receiver", + "receiver-open-dingtalk-id" ] ] }, - "description": "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId/openDingTalkId 二选一。单聊目标会统一解析后以 receiverOpenDingTalkId 传给下层;userId 包括在 --dry-run 时也会先做只读通讯录解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。\n\n参数约束:\n - --group、--receiver 必须且只能指定一个\n - --flow-status 必须在 1-5 之间,且显式指定时必须同时提供 --content", + "description": "当你要发送一张流式卡片消息时使用;群 openConversationId、单聊 userId、单聊 openDingTalkId 严格三选一,分别使用 --group、--receiver、--receiver-open-dingtalk-id。--receiver 始终按 userId 做只读通讯录解析,即使值以 D/d 开头也不会猜成 openDingTalkId;已有 openDingTalkId 时必须用显式参数直传。userId 包括在 --dry-run 时也会先解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。\n\n参数约束:\n - --group、--receiver、--receiver-open-dingtalk-id 必须且只能指定一个\n - --flow-status 必须在 1-5 之间,且显式指定时必须同时提供 --content", "display": "群聊 / 消息 / 机器人", "effect": "write", "effect_source": "agent-hint", @@ -77372,7 +77374,7 @@ "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.", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, explicit userId/openDingTalkId routing, two-action dry-run, bizId extraction, and partial-failure reporting.", "selected": true, "source": "internal/cli/schema_hints/selection/chat.json", "value": "创建流式卡片,可在同一次调用中写入内容并结束" @@ -77380,7 +77382,7 @@ ], "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.", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, explicit userId/openDingTalkId routing, two-action dry-run, bizId extraction, and partial-failure reporting.", "source": "internal/cli/schema_hints/selection/chat.json", "value": "创建流式卡片,可在同一次调用中写入内容并结束" }, @@ -77388,7 +77390,7 @@ "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": "available" @@ -77396,7 +77398,7 @@ ], "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": "available" }, @@ -77404,7 +77406,7 @@ "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.", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, explicit userId/openDingTalkId routing, two-action dry-run, bizId extraction, and partial-failure reporting.", "selected": true, "source": "internal/cli/schema_hints/selection/chat.json", "value": [ @@ -77414,7 +77416,7 @@ ], "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.", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, explicit userId/openDingTalkId routing, two-action dry-run, bizId extraction, and partial-failure reporting.", "source": "internal/cli/schema_hints/selection/chat.json", "value": [ "已有 bizId、只需要追加或更新现有卡片内容时使用 +messages-update-card" @@ -77428,6 +77430,14 @@ "source": "reviewed_command_registry", "source_ref": "chat +messages-send-card", "value": "chat.shortcut_messages_send_card" + }, + { + "precedence": "reviewed_manual", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", + "selected": false, + "source": "reviewed_manual_hint", + "source_ref": "chat +messages-send-card", + "value": "chat.shortcut_messages_send_card" } ], "precedence": "command_registry", @@ -77440,7 +77450,7 @@ "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": "user_required" @@ -77448,7 +77458,7 @@ ], "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": "user_required" }, @@ -77458,19 +77468,19 @@ "precedence": "cobra_help", "selected": true, "source": "cobra_help", - "value": "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId/openDingTalkId 二选一。单聊目标会统一解析后以 receiverOpenDingTalkId 传给下层;userId 包括在 --dry-run 时也会先做只读通讯录解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。\n\n参数约束:\n - --group、--receiver 必须且只能指定一个\n - --flow-status 必须在 1-5 之间,且显式指定时必须同时提供 --content" + "value": "当你要发送一张流式卡片消息时使用;群 openConversationId、单聊 userId、单聊 openDingTalkId 严格三选一,分别使用 --group、--receiver、--receiver-open-dingtalk-id。--receiver 始终按 userId 做只读通讯录解析,即使值以 D/d 开头也不会猜成 openDingTalkId;已有 openDingTalkId 时必须用显式参数直传。userId 包括在 --dry-run 时也会先解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。\n\n参数约束:\n - --group、--receiver、--receiver-open-dingtalk-id 必须且只能指定一个\n - --flow-status 必须在 1-5 之间,且显式指定时必须同时提供 --content" } ], "precedence": "cobra_help", "resolution": "highest_precedence", "source": "cobra_help", - "value": "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId/openDingTalkId 二选一。单聊目标会统一解析后以 receiverOpenDingTalkId 传给下层;userId 包括在 --dry-run 时也会先做只读通讯录解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。\n\n参数约束:\n - --group、--receiver 必须且只能指定一个\n - --flow-status 必须在 1-5 之间,且显式指定时必须同时提供 --content" + "value": "当你要发送一张流式卡片消息时使用;群 openConversationId、单聊 userId、单聊 openDingTalkId 严格三选一,分别使用 --group、--receiver、--receiver-open-dingtalk-id。--receiver 始终按 userId 做只读通讯录解析,即使值以 D/d 开头也不会猜成 openDingTalkId;已有 openDingTalkId 时必须用显式参数直传。userId 包括在 --dry-run 时也会先解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。\n\n参数约束:\n - --group、--receiver、--receiver-open-dingtalk-id 必须且只能指定一个\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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": "write" @@ -77478,7 +77488,7 @@ ], "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": "write" }, @@ -77486,7 +77496,7 @@ "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.", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, explicit userId/openDingTalkId routing, two-action dry-run, bizId extraction, and partial-failure reporting.", "selected": true, "source": "internal/cli/schema_hints/selection/chat.json", "value": [ @@ -77497,7 +77507,7 @@ ], "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.", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, explicit userId/openDingTalkId routing, 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 \"任务已完成\"", @@ -77508,7 +77518,7 @@ "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": "unknown" @@ -77516,7 +77526,7 @@ ], "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": "unknown" }, @@ -77524,7 +77534,7 @@ "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": "composite" @@ -77532,7 +77542,7 @@ ], "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": "composite" }, @@ -77540,7 +77550,7 @@ "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "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." @@ -77548,7 +77558,7 @@ ], "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "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." }, @@ -77572,14 +77582,14 @@ "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "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.", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, explicit userId/openDingTalkId routing, two-action dry-run, bizId extraction, and partial-failure reporting.", "selected": false, "source": "internal/cli/schema_hints/selection/chat.json", "value": true @@ -77587,7 +77597,7 @@ ], "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": true }, @@ -77595,7 +77605,7 @@ "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": "medium" @@ -77603,7 +77613,7 @@ ], "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": "medium" }, @@ -77625,20 +77635,20 @@ "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.", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, explicit userId/openDingTalkId routing, two-action dry-run, bizId extraction, and partial-failure reporting.", "selected": true, "source": "internal/cli/schema_hints/selection/chat.json", "value": [ - "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId/openDingTalkId 二选一。单聊目标会统一解析后以 receiverOpenDingTalkId 传给下层;userId 包括在 --dry-run 时也会先做只读通讯录解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。" + "当你要发送一张流式卡片消息时使用;群 openConversationId、单聊 userId、单聊 openDingTalkId 严格三选一,分别使用 --group、--receiver、--receiver-open-dingtalk-id。--receiver 始终按 userId 做只读通讯录解析,即使值以 D/d 开头也不会猜成 openDingTalkId;已有 openDingTalkId 时必须用显式参数直传。userId 包括在 --dry-run 时也会先解析。只传目标时创建卡片并返回 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.", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, explicit userId/openDingTalkId routing, two-action dry-run, bizId extraction, and partial-failure reporting.", "source": "internal/cli/schema_hints/selection/chat.json", "value": [ - "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId/openDingTalkId 二选一。单聊目标会统一解析后以 receiverOpenDingTalkId 传给下层;userId 包括在 --dry-run 时也会先做只读通讯录解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。" + "当你要发送一张流式卡片消息时使用;群 openConversationId、单聊 userId、单聊 openDingTalkId 严格三选一,分别使用 --group、--receiver、--receiver-open-dingtalk-id。--receiver 始终按 userId 做只读通讯录解析,即使值以 D/d 开头也不会猜成 openDingTalkId;已有 openDingTalkId 时必须用显式参数直传。userId 包括在 --dry-run 时也会先解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。" ] } }, @@ -77648,7 +77658,7 @@ "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, + "parameter_count": 5, "parameters": { "content": { "description": "创建后立即写入的卡片内容;省略时仅创建并返回 bizId", @@ -77826,7 +77836,7 @@ "type": "integer" }, "group": { - "description": "群 openConversationId(与 --receiver 互斥)", + "description": "群 openConversationId(与两个单聊接收者参数互斥)", "field_provenance": { "description": { "candidates": [ @@ -77834,7 +77844,7 @@ "precedence": "cobra_contract", "selected": true, "source": "cobra_usage", - "value": "群 openConversationId(与 --receiver 互斥)" + "value": "群 openConversationId(与两个单聊接收者参数互斥)" }, { "precedence": "default", @@ -77846,7 +77856,7 @@ "precedence": "cobra_contract", "resolution": "highest_precedence", "source": "cobra_usage", - "value": "群 openConversationId(与 --receiver 互斥)" + "value": "群 openConversationId(与两个单聊接收者参数互斥)" }, "property": { "candidates": [ @@ -77916,7 +77926,7 @@ "type": "string" }, "receiver": { - "description": "单聊接收者 userId 或 openDingTalkId(与 --group 互斥);userId 包括在 --dry-run 时也会先只读解析", + "description": "单聊接收者 userId(与 --group/--receiver-open-dingtalk-id 互斥);始终只读解析为 openDingTalkId,包括 --dry-run 和 D/d 开头的 userId", "field_provenance": { "description": { "candidates": [ @@ -77924,7 +77934,7 @@ "precedence": "cobra_contract", "selected": true, "source": "cobra_usage", - "value": "单聊接收者 userId 或 openDingTalkId(与 --group 互斥);userId 包括在 --dry-run 时也会先只读解析" + "value": "单聊接收者 userId(与 --group/--receiver-open-dingtalk-id 互斥);始终只读解析为 openDingTalkId,包括 --dry-run 和 D/d 开头的 userId" }, { "precedence": "default", @@ -77936,7 +77946,7 @@ "precedence": "cobra_contract", "resolution": "highest_precedence", "source": "cobra_usage", - "value": "单聊接收者 userId 或 openDingTalkId(与 --group 互斥);userId 包括在 --dry-run 时也会先只读解析" + "value": "单聊接收者 userId(与 --group/--receiver-open-dingtalk-id 互斥);始终只读解析为 openDingTalkId,包括 --dry-run 和 D/d 开头的 userId" }, "property": { "candidates": [ @@ -78004,6 +78014,112 @@ "property": "receiver", "required": false, "type": "string" + }, + "receiver-open-dingtalk-id": { + "description": "单聊接收者 openDingTalkId(与 --group/--receiver 互斥);显式直传且不做通讯录解析", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "单聊接收者 openDingTalkId(与 --group/--receiver 互斥);显式直传且不做通讯录解析" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "单聊接收者 openDingTalkId(与 --group/--receiver 互斥);显式直传且不做通讯录解析" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "receiverOpenDingTalkId" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "receiverOpenDingtalkId" + } + ], + "precedence": "reviewed_manual", + "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; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", + "source": "reviewed_manual_hint", + "value": "receiverOpenDingTalkId" + }, + "required": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", + "selected": true, + "source": "reviewed_manual_hint", + "value": false + }, + { + "precedence": "command_constraint", + "selected": false, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "reviewed_manual", + "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; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", + "source": "reviewed_manual_hint", + "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": "receiverOpenDingTalkId", + "required": false, + "type": "string" } }, "path": "chat.shortcut_messages_send_card", @@ -78014,7 +78130,7 @@ "source": "reviewed_command_registry", "title": "创建流式卡片,可在同一次调用中写入内容并结束", "use_when": [ - "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId/openDingTalkId 二选一。单聊目标会统一解析后以 receiverOpenDingTalkId 传给下层;userId 包括在 --dry-run 时也会先做只读通讯录解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。" + "当你要发送一张流式卡片消息时使用;群 openConversationId、单聊 userId、单聊 openDingTalkId 严格三选一,分别使用 --group、--receiver、--receiver-open-dingtalk-id。--receiver 始终按 userId 做只读通讯录解析,即使值以 D/d 开头也不会猜成 openDingTalkId;已有 openDingTalkId 时必须用显式参数直传。userId 包括在 --dry-run 时也会先解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。" ] }, "chat.shortcut_messages_update_card": { diff --git a/internal/cli/schema_hints/metadata/chat.json b/internal/cli/schema_hints/metadata/chat.json index b578f347..3aa3f546 100644 --- a/internal/cli/schema_hints/metadata/chat.json +++ b/internal/cli/schema_hints/metadata/chat.json @@ -1199,8 +1199,14 @@ "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.", + "parameters": { + "receiver-open-dingtalk-id": { + "property": "receiverOpenDingTalkId", + "required": false + } + }, "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.", + "review_reason": "Reviewed against create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property.", "cli_path": "chat +messages-send-card", "runtime_gate": "typed_yes" }, diff --git a/internal/cli/schema_hints/selection/chat.json b/internal/cli/schema_hints/selection/chat.json index 341cf8b7..789db46c 100644 --- a/internal/cli/schema_hints/selection/chat.json +++ b/internal/cli/schema_hints/selection/chat.json @@ -2172,7 +2172,7 @@ "chat.shortcut_messages_send_card": { "agent_summary": "创建流式卡片,可在同一次调用中写入内容并结束", "use_when": [ - "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId/openDingTalkId 二选一。单聊目标会统一解析后以 receiverOpenDingTalkId 传给下层;userId 包括在 --dry-run 时也会先做只读通讯录解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。" + "当你要发送一张流式卡片消息时使用;群 openConversationId、单聊 userId、单聊 openDingTalkId 严格三选一,分别使用 --group、--receiver、--receiver-open-dingtalk-id。--receiver 始终按 userId 做只读通讯录解析,即使值以 D/d 开头也不会猜成 openDingTalkId;已有 openDingTalkId 时必须用显式参数直传。userId 包括在 --dry-run 时也会先解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。" ], "avoid_when": [ "已有 bizId、只需要追加或更新现有卡片内容时使用 +messages-update-card" @@ -2182,7 +2182,7 @@ "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.", + "review_reason": "Agent-authored from the verified create-only and create-then-update lifecycle, explicit userId/openDingTalkId routing, 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", diff --git a/internal/shortcut/chat/chat_message.go b/internal/shortcut/chat/chat_message.go index 3e0f3f7f..50edbe40 100644 --- a/internal/shortcut/chat/chat_message.go +++ b/internal/shortcut/chat/chat_message.go @@ -660,6 +660,19 @@ func DownloadMessageResources( "resources": resources, } } + if len(resources) == 0 { + return map[string]any{ + "ok": true, + "partial": false, + "discoveredCount": discoveredCount, + "requestedCount": 0, + "deduplicatedCount": discoveredCount, + "downloadedCount": 0, + "failedCount": 0, + "downloads": []map[string]any{}, + "failures": []map[string]any{}, + } + } cwd, err := resourceGetwd() if err != nil { @@ -1002,16 +1015,17 @@ var MessagesSendCard = shortcut.Shortcut{ Command: "+messages-send-card", Product: "im", Description: "创建流式卡片,可在同一次调用中写入内容并结束", - Intent: "当你要发送一张流式卡片消息时使用;群 openConversationId 或单聊接收者 userId/openDingTalkId 二选一。单聊目标会统一解析后以 receiverOpenDingTalkId 传给下层;userId 包括在 --dry-run 时也会先做只读通讯录解析。只传目标时创建卡片并返回 bizId,供后续 messages-update-card 流式更新;同时传 --content 时会自动串联创建和更新,默认以 flowStatus=3 完成,避免卡片停留在加载中。", + Intent: "当你要发送一张流式卡片消息时使用;群 openConversationId、单聊 userId、单聊 openDingTalkId 严格三选一,分别使用 --group、--receiver、--receiver-open-dingtalk-id。--receiver 始终按 userId 做只读通讯录解析,即使值以 D/d 开头也不会猜成 openDingTalkId;已有 openDingTalkId 时必须用显式参数直传。userId 包括在 --dry-run 时也会先解析。只传目标时创建卡片并返回 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 或 openDingTalkId(与 --group 互斥);userId 包括在 --dry-run 时也会先只读解析"}, + {Name: "group", Type: shortcut.FlagString, Desc: "群 openConversationId(与两个单聊接收者参数互斥)"}, + {Name: "receiver", Type: shortcut.FlagString, Desc: "单聊接收者 userId(与 --group/--receiver-open-dingtalk-id 互斥);始终只读解析为 openDingTalkId,包括 --dry-run 和 D/d 开头的 userId"}, + {Name: "receiver-open-dingtalk-id", Type: shortcut.FlagString, Desc: "单聊接收者 openDingTalkId(与 --group/--receiver 互斥);显式直传且不做通讯录解析"}, {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.ConstraintExactlyOne, Flags: []string{"group", "receiver", "receiver-open-dingtalk-id"}}, { Kind: shortcut.ConstraintCustom, Flags: []string{"flow-status"}, @@ -1034,21 +1048,19 @@ var MessagesSendCard = shortcut.Shortcut{ Execute: func(rt *shortcut.RuntimeContext) error { group := rt.Str("group") receiver := rt.Str("receiver") - if group == "" && receiver == "" { - return fmt.Errorf("--group 或 --receiver 必填其一") - } - if group != "" && receiver != "" { - return fmt.Errorf("--group 与 --receiver 互斥") - } + receiverOpenID := rt.Str("receiver-open-dingtalk-id") params := map[string]any{} - if group != "" { + switch { + case group != "": params["openConversationId"] = group - } else { + case receiver != "": openID, err := resolveUserOpenDingTalkID(rt, receiver) if err != nil { return err } params["receiverOpenDingTalkId"] = openID + default: + params["receiverOpenDingTalkId"] = receiverOpenID } content := rt.Str("content") if content == "" { diff --git a/internal/shortcut/chat/chat_message_coverage_test.go b/internal/shortcut/chat/chat_message_coverage_test.go index 3ed9ddd1..cf81938d 100644 --- a/internal/shortcut/chat/chat_message_coverage_test.go +++ b/internal/shortcut/chat/chat_message_coverage_test.go @@ -89,6 +89,39 @@ func TestCrossPlatformCoverageMgetResourceDownloadOutcomes(t *testing.T) { t.Fatalf("getwd ledger = %#v", ledger) } }) + t.Run("zero resources skip getwd", func(t *testing.T) { + resetResourceDownloadHooks(t) + getwdCalled := false + resourceGetwd = func() (string, error) { + getwdCalled = true + return "", errors.New("getwd") + } + helpers.InitDeps(&larkAlignmentCaller{responses: map[string]string{ + "im/list_messages_by_ids": `{"result":[{"openMessageId":"msg","openConversationId":"cid","content":"plain text"}]}`, + }}) + root := newPlatformCoverageRoot() + var output bytes.Buffer + root.SetOut(&output) + root.SetArgs(baseArgs) + if err := root.Execute(); err != nil { + t.Fatalf("zero-resource download error = %v", err) + } + if getwdCalled { + t.Fatal("zero-resource download unnecessarily read the working directory") + } + var payload map[string]any + if err := json.Unmarshal(output.Bytes(), &payload); err != nil { + t.Fatal(err) + } + ledger, _ := payload["resourceDownloads"].(map[string]any) + failures, _ := ledger["failures"].([]any) + if ledger["ok"] != true || + ledger["requestedCount"] != float64(0) || + ledger["failedCount"] != float64(0) || + len(failures) != 0 { + t.Fatalf("zero-resource ledger = %#v", ledger) + } + }) cases := []struct { name string diff --git a/internal/shortcut/chat/gap_fill_test.go b/internal/shortcut/chat/gap_fill_test.go index 49a81980..b74df949 100644 --- a/internal/shortcut/chat/gap_fill_test.go +++ b/internal/shortcut/chat/gap_fill_test.go @@ -56,8 +56,8 @@ func TestMessagesSendPublishesCompleteIdentityConstraintInputs(t *testing.T) { func TestCrossPlatformCoverageSafeResourceDownloadsStayReadOnly(t *testing.T) { for _, command := range []shortcut.Shortcut{MessagesMget, MessagesResourceDownload} { - if command.Risk != shortcut.RiskRead || command.RiskWhen != nil { - t.Errorf("%s risk contract = %q, dynamic=%v", command.Command, command.Risk, command.RiskWhen != nil) + if command.Risk != shortcut.RiskRead { + t.Errorf("%s risk contract = %q", command.Command, command.Risk) } } } @@ -386,7 +386,7 @@ func TestCrossPlatformCoverageMessagesSendCardResolvesReceiverForLowerTool(t *te root := newPlatformCoverageRoot() root.SetArgs([]string{ "chat", "+messages-send-card", - "--receiver", "user-id", + "--receiver", "d-user-id", "--yes", }) if err := root.Execute(); err != nil { @@ -402,18 +402,21 @@ func TestCrossPlatformCoverageMessagesSendCardResolvesReceiverForLowerTool(t *te if got := fake.calls[1].args["receiverOpenDingTalkId"]; got != "D-resolved" { t.Fatalf("receiverOpenDingTalkId = %#v, want D-resolved", got) } + if got := fake.calls[0].args["user_id_list"]; !reflect.DeepEqual(got, []string{"d-user-id"}) { + t.Fatalf("D/d-prefixed userId resolution args = %#v", fake.calls[0].args) + } if _, exists := fake.calls[1].args["receiverUid"]; exists { t.Fatalf("obsolete receiverUid leaked to lower tool: %#v", fake.calls[1].args) } } -func TestCrossPlatformCoverageMessagesSendCardKeepsOpenReceiver(t *testing.T) { +func TestCrossPlatformCoverageMessagesSendCardUsesExplicitOpenReceiver(t *testing.T) { fake := &larkAlignmentCaller{} helpers.InitDeps(fake) root := newPlatformCoverageRoot() root.SetArgs([]string{ "chat", "+messages-send-card", - "--receiver", "D-direct", + "--receiver-open-dingtalk-id", "D-direct", "--yes", }) if err := root.Execute(); err != nil { @@ -547,6 +550,8 @@ func TestCrossPlatformCoverageMessagesSendCardDryRunAndFailureBoundaries(t *test for _, args := range [][]string{ {"--group", "cid", "--content", "x", "--flow-status", "6"}, {"--group", "cid", "--flow-status", "2"}, + {"--group", "cid", "--receiver-open-dingtalk-id", "D-direct"}, + {"--receiver", "user-id", "--receiver-open-dingtalk-id", "D-direct"}, } { helpers.InitDeps(&larkAlignmentCaller{}) root := newPlatformCoverageRoot() diff --git a/internal/shortcut/chat/unified_send.go b/internal/shortcut/chat/unified_send.go index 9d818a94..72b91702 100644 --- a/internal/shortcut/chat/unified_send.go +++ b/internal/shortcut/chat/unified_send.go @@ -368,9 +368,6 @@ func messagesSendUserTarget(rt *shortcut.RuntimeContext) (group, openID string, func resolveUserOpenDingTalkID(rt *shortcut.RuntimeContext, userID string) (string, error) { userID = strings.TrimSpace(userID) - if isOpenID(userID) { - return userID, nil - } data, err := rt.CallMCPData("contact", "get_user_info_by_user_ids", map[string]any{ "user_id_list": []string{userID}, }) @@ -379,7 +376,7 @@ func resolveUserOpenDingTalkID(rt *shortcut.RuntimeContext, userID string) (stri } openID := findOpenDingTalkID(data) if openID == "" { - return "", apperrors.NewValidation("无法把 userId 解析为 openDingTalkId;请改传 openDingTalkId") + return "", apperrors.NewValidation("无法把 userId 解析为 openDingTalkId;请改用当前命令提供的显式 openDingTalkId 参数") } return openID, nil } diff --git a/internal/shortcut/runner.go b/internal/shortcut/runner.go index d81fa439..30fd411a 100644 --- a/internal/shortcut/runner.go +++ b/internal/shortcut/runner.go @@ -461,7 +461,7 @@ func validateConstraints(rt *RuntimeContext, s Shortcut) error { // --dry-run is set. Read-only shortcuts never prompt. Returns false when the // user declines. func confirmRisk(rt *RuntimeContext, s Shortcut) bool { - risk := s.effectiveRisk(rt) + risk := s.risk() if risk == RiskRead || rt.DryRun() || rt.Yes() { return true } diff --git a/internal/shortcut/shortcut_test.go b/internal/shortcut/shortcut_test.go index da94101b..4ddd607d 100644 --- a/internal/shortcut/shortcut_test.go +++ b/internal/shortcut/shortcut_test.go @@ -41,73 +41,15 @@ func TestCrossPlatformCoverageRiskDefaultsToRead(t *testing.T) { } } -func TestCrossPlatformCoverageEffectiveRiskUsesParsedFlagsAndFailsClosed(t *testing.T) { +func TestCrossPlatformCoverageConfirmRiskPromptsForStaticWrite(t *testing.T) { cmd := &cobra.Command{} - cmd.Flags().Bool("download-resources", false, "") - rt := &RuntimeContext{cmd: cmd} - s := Shortcut{ - Risk: RiskRead, - RiskWhen: func(rt *RuntimeContext) Risk { - if rt.Bool("download-resources") { - return RiskWrite - } - return RiskRead - }, - } - if got := s.effectiveRisk(rt); got != RiskRead { - t.Fatalf("default effectiveRisk() = %q, want read", got) - } - if err := cmd.Flags().Set("download-resources", "true"); err != nil { - t.Fatal(err) - } - if got := s.effectiveRisk(rt); got != RiskWrite { - t.Fatalf("download effectiveRisk() = %q, want write", got) - } - - s.Risk = RiskRead - s.RiskWhen = func(*RuntimeContext) Risk { return Risk("unknown") } - if got := s.effectiveRisk(rt); got != RiskHighWrite { - t.Fatalf("invalid callback effectiveRisk() = %q, want high-risk-write", got) - } - s.Risk = RiskWrite - s.RiskWhen = func(*RuntimeContext) Risk { return RiskHighWrite } - if got := s.effectiveRisk(rt); got != RiskHighWrite { - t.Fatalf("write-to-high upgrade effectiveRisk() = %q, want high-risk-write", got) - } - s.RiskWhen = func(*RuntimeContext) Risk { return RiskRead } - if got := s.effectiveRisk(rt); got != RiskWrite { - t.Fatalf("write-to-read downgrade effectiveRisk() = %q, want write", got) - } - s.Risk = Risk("unknown") - if got := s.effectiveRisk(rt); got != RiskHighWrite { - t.Fatalf("invalid base effectiveRisk() = %q, want high-risk-write", got) - } - s.Risk = RiskHighWrite - s.RiskWhen = func(*RuntimeContext) Risk { return RiskRead } - if got := s.effectiveRisk(rt); got != RiskHighWrite { - t.Fatalf("high-risk downgrade effectiveRisk() = %q, want high-risk-write", got) - } -} - -func TestCrossPlatformCoverageConfirmRiskUsesEffectiveRisk(t *testing.T) { - cmd := &cobra.Command{} - cmd.Flags().Bool("download-resources", false, "") - if err := cmd.Flags().Set("download-resources", "true"); err != nil { - t.Fatal(err) - } var stderr bytes.Buffer cmd.SetErr(&stderr) rt := &RuntimeContext{cmd: cmd} s := Shortcut{ Service: "chat", - Command: "+messages-mget", - Risk: RiskRead, - RiskWhen: func(rt *RuntimeContext) Risk { - if rt.Bool("download-resources") { - return RiskWrite - } - return RiskRead - }, + Command: "+messages-send", + Risk: RiskWrite, } reader, writer, err := os.Pipe() @@ -128,53 +70,13 @@ func TestCrossPlatformCoverageConfirmRiskUsesEffectiveRisk(t *testing.T) { t.Fatal(err) } if !confirmRisk(rt, s) { - t.Fatal("dynamic write risk was not confirmed") + t.Fatal("static write risk was not confirmed") } - if got := stderr.String(); !strings.Contains(got, "chat +messages-mget(write)") { + if got := stderr.String(); !strings.Contains(got, "chat +messages-send(write)") { t.Fatalf("confirmation prompt = %q", got) } } -func TestConfirmRiskDoesNotReadStdinForDynamicReadInvocation(t *testing.T) { - cmd := &cobra.Command{} - cmd.Flags().Bool("download-resources", false, "") - var stderr bytes.Buffer - cmd.SetErr(&stderr) - rt := &RuntimeContext{cmd: cmd} - s := Shortcut{ - Service: "chat", - Command: "+at-me", - Risk: RiskRead, - RiskWhen: func(rt *RuntimeContext) Risk { - if rt.Bool("download-resources") { - return RiskWrite - } - return RiskRead - }, - } - - reader, writer, err := os.Pipe() - if err != nil { - t.Fatal(err) - } - if err := writer.Close(); err != nil { - t.Fatal(err) - } - previousStdin := os.Stdin - os.Stdin = reader - t.Cleanup(func() { - os.Stdin = previousStdin - _ = reader.Close() - }) - - if !confirmRisk(rt, s) { - t.Fatal("dynamic read invocation was treated as a declined write") - } - if stderr.Len() != 0 { - t.Fatalf("dynamic read invocation prompted: %q", stderr.String()) - } -} - func TestCrossPlatformCoverageMountRegistersFlagsAndUse(t *testing.T) { s := Shortcut{ Service: "contact", diff --git a/internal/shortcut/smart/gap_fill_test.go b/internal/shortcut/smart/gap_fill_test.go index bab0a087..049e1894 100644 --- a/internal/shortcut/smart/gap_fill_test.go +++ b/internal/shortcut/smart/gap_fill_test.go @@ -15,8 +15,8 @@ import ( func TestCrossPlatformCoverageSafeResourceQueryDownloadsStayReadOnly(t *testing.T) { for _, command := range []shortcut.Shortcut{AtMe, ChatMessages, SearchMsg, ThreadReplies} { - if command.Risk != shortcut.RiskRead || command.RiskWhen != nil { - t.Errorf("%s risk contract = %q, dynamic=%v", command.Command, command.Risk, command.RiskWhen != nil) + if command.Risk != shortcut.RiskRead { + t.Errorf("%s risk contract = %q", command.Command, command.Risk) } } } diff --git a/internal/shortcut/types.go b/internal/shortcut/types.go index 64dd1625..f0ca09cf 100644 --- a/internal/shortcut/types.go +++ b/internal/shortcut/types.go @@ -138,13 +138,6 @@ type Shortcut struct { Intent string // Risk classifies the side effect; defaults to RiskRead when empty. Risk Risk - // RiskWhen optionally refines Risk after flags have been parsed and - // validated. It is intended for commands that are read-only by default but - // gain a local or remote write mode behind an explicit opt-in flag. - // - // Distribution Schema keeps publishing the stable default Risk contract; - // runtime confirmation uses the value returned here. - RiskWhen func(rt *RuntimeContext) Risk // Flags are the command-specific flags. Global flags are injected separately. Flags []Flag // Constraints publish and enforce relationships that individual flags cannot @@ -199,33 +192,3 @@ func (s Shortcut) risk() Risk { } return s.Risk } - -// effectiveRisk returns the invocation-specific risk after flags are resolved. -// Invalid callback/base values fail closed to high-risk-write, while attempted -// downgrades preserve the declarative default instead of weakening its -// confirmation policy. -func (s Shortcut) effectiveRisk(rt *RuntimeContext) Risk { - base := s.risk() - if s.RiskWhen == nil { - return base - } - dynamic := s.RiskWhen(rt) - switch dynamic { - case RiskRead, RiskWrite, RiskHighWrite: - default: - return RiskHighWrite - } - switch base { - case RiskHighWrite: - return RiskHighWrite - case RiskWrite: - if dynamic == RiskHighWrite { - return RiskHighWrite - } - return RiskWrite - case RiskRead: - return dynamic - default: - return RiskHighWrite - } -} diff --git a/skills/mono/references/intent-guide.md b/skills/mono/references/intent-guide.md index f0dff04e..e28c4fc7 100644 --- a/skills/mono/references/intent-guide.md +++ b/skills/mono/references/intent-guide.md @@ -289,6 +289,12 @@ dws chat +messages-send --as user --open-dingtalk-id --msg-type `+messages-send` 的 @ 占位符按身份自动规范化并补齐:user 使用 `<@id>` / `<@all>`;bot/webhook 使用 `@id` / `@手机号` / `@all`。声明 `--at-*` / `--at-all` 即可,不要再手工拼 `@10`。 +**用 `chat +messages-send-card` 的场景**: +- 群聊流式卡片使用 `--group `。 +- 单聊已有 userId 时使用 `--receiver `,CLI 始终先只读解析为 openDingTalkId;即使 userId 以 D/d 开头也不会猜测类型,`--dry-run` 也会执行该解析。 +- 单聊已有 openDingTalkId 时必须显式使用 `--receiver-open-dingtalk-id `,避免与 userId 混淆。 +- `--group`、`--receiver`、`--receiver-open-dingtalk-id` 严格三选一;传 `--content` 可在同一次调用中创建并结束卡片。 + **用 `chat +messages-send --as bot` 的场景**: - "让机器人在群里发一条通知" — **机器人身份**发消息 - "给张三发一条机器人单聊消息" — 机器人单聊 diff --git a/skills/multi/dingtalk-chat/SKILL.md b/skills/multi/dingtalk-chat/SKILL.md index 1023fe76..208c066e 100644 --- a/skills/multi/dingtalk-chat/SKILL.md +++ b/skills/multi/dingtalk-chat/SKILL.md @@ -105,7 +105,7 @@ metadata: | `dws chat +messages-remove-emoji` | write | 移除消息的 emoji 表情回应 | | `dws chat +messages-remove-text-emotion` | write | 移除消息的文字表情回应 | | `dws chat +messages-reply` | write | 以当前用户身份引用回复消息(自动补全原发送者) | -| `dws chat +messages-resource-download` | write | 安全下载消息资源(图片/视频/语音/文件)到本地 | +| `dws chat +messages-resource-download` | read | 安全下载消息资源(图片/视频/语音/文件)到工作目录内,无需交互确认 | | `dws chat +messages-resource-url` | read | 获取消息资源(图片/视频/语音)下载链接 | | `dws chat +messages-send` | write | 统一发送文本、Markdown、当前用户文件或已有 mediaId 图片 | | `dws chat +messages-send-by-bot` | write | 机器人向群聊发送 Markdown 消息 | @@ -130,6 +130,7 @@ metadata: - 查询结果中的 `resourceRefs` 是可继续执行的资源上下文。引用、回复或合并转发中的子消息必须使用该子消息返回的 `messageId`;子消息缺会话 ID 时才继承父消息的 `openConversationId`。 - 上述五个查询 Shortcut 与 `+messages-resource-download` 都沿用安全本地下载的 `read/not_required` 契约,不应添加 `--yes` 或触发交互确认。下载只允许工作目录内相对路径、默认不覆盖并原子落盘;需要覆盖时必须由用户显式传 `--overwrite`。 - `+messages-send` 会按身份规范化 @ 占位符:user 使用 `<@id>` / `<@all>`,bot/webhook 使用 `@id` / `@手机号` / `@all`;只需声明 `--at-*` / `--at-all`,缺失占位符会自动补齐。 +- 流式卡片优先用 `+messages-send-card`:群聊传 `--group`,单聊 userId 传 `--receiver` 并由 CLI 只读解析,已有 openDingTalkId 则必须显式传 `--receiver-open-dingtalk-id`;三者严格三选一,禁止根据首字母猜身份类型。 ## 意图表 @@ -145,6 +146,7 @@ metadata: | "查看我收藏的消息" | `dws chat +flag-list` | | "用机器人发消息" | `dws chat +messages-send --as bot --robot-code --chat-id --text "<内容>"` | | "Webhook 推一条" | `dws chat +messages-send --as webhook --webhook-token --text "<内容>"` | +| "发送流式卡片" | `dws chat +messages-send-card --group --content "<内容>"`;单聊 userId 改用 `--receiver`,openDingTalkId 改用 `--receiver-open-dingtalk-id` | | "撤回消息" | `dws chat message recall --conversation-id --msg-id ` | | "标记未读 / 清除红点 / 全部已读" | `dws chat mark-unread` / `dws chat clear-red-point` / `dws chat clear-all-red-point` | | "置顶某条消息 / 取消消息置顶" | `dws chat message set-top-msg` / `dws chat message unset-top-msg` | diff --git a/skills/multi/dws-shared/references/intent-guide.md b/skills/multi/dws-shared/references/intent-guide.md index 76514a0e..7ef3648e 100644 --- a/skills/multi/dws-shared/references/intent-guide.md +++ b/skills/multi/dws-shared/references/intent-guide.md @@ -301,6 +301,12 @@ dws chat message send --open-dingtalk-id --msg-type file --file > ❌ 反模式:调 `dt_media_upload` / `extract_media_id.py` / `drive upload` / `drive download` 等前置工具再 `--msg-type image --media-id`。这是**旧链路**,仅当上游已持有 mediaId 才用;新场景一律 `--file-path` 直发,避免长链路与“空白图”现象。 > 富媒体消息单聊优先使用 `--open-dingtalk-id`;传 `--user` 时 CLI 会尝试解析为 openDingTalkId 后发送。 +**用 `chat +messages-send-card` 的场景**: +- 群聊流式卡片使用 `--group `。 +- 单聊已有 userId 时使用 `--receiver `,CLI 始终先只读解析为 openDingTalkId;即使 userId 以 D/d 开头也不会猜测类型,`--dry-run` 也会执行该解析。 +- 单聊已有 openDingTalkId 时必须显式使用 `--receiver-open-dingtalk-id `,避免与 userId 混淆。 +- `--group`、`--receiver`、`--receiver-open-dingtalk-id` 严格三选一;传 `--content` 可在同一次调用中创建并结束卡片。 + - "发送位置/坐标/地址到群里" / "发个位置给某某" — `dws chat message send ... --msg-type location --latitude <纬度> --longitude <经度> --location-name <地址名称> --map-thumbnail-url @mediaId`;地图缩略图需先通过 `dt_media_upload` 上传获取 mediaId ```bash