diff --git a/internal/cli/schema_agent_metadata/chat.json b/internal/cli/schema_agent_metadata/chat.json index 9c2cb7ca..4dae7162 100644 --- a/internal/cli/schema_agent_metadata/chat.json +++ b/internal/cli/schema_agent_metadata/chat.json @@ -8,8 +8,8 @@ "avoid_when": [ "需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令" ], - "confirmation": "not_required", - "effect": "read", + "confirmation": "user_required", + "effect": "write", "effect_source": "agent-hint", "examples": [ "dws chat +at-me", @@ -37,14 +37,14 @@ "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { "value": "available", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity." + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -69,34 +69,34 @@ ] }, "confirmation": { - "value": "not_required", + "value": "user_required", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { - "value": "not_required", + "value": "user_required", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity." + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files." } ] }, "effect": { - "value": "read", + "value": "write", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { - "value": "read", + "value": "write", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity." + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -123,18 +123,18 @@ ] }, "idempotency": { - "value": "idempotent", + "value": "unknown", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { - "value": "idempotent", + "value": "unknown", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity." + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -143,14 +143,14 @@ "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { "value": "composite", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity." + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -159,14 +159,14 @@ "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { "value": "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.", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity." + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -191,14 +191,14 @@ "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { "value": true, "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity." + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files." }, { "value": true, @@ -210,24 +210,24 @@ ] }, "risk": { - "value": "low", + "value": "medium", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { - "value": "low", + "value": "medium", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity." + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files." } ] }, "use_when": { "value": [ - "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。" + "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。默认只读且不会发送、撤回或标记任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ], "source": "internal/cli/schema_hints/selection/chat.json", "precedence": "reviewed_explicit", @@ -236,7 +236,7 @@ "candidates": [ { "value": [ - "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。" + "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。默认只读且不会发送、撤回或标记任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ], "source": "internal/cli/schema_hints/selection/chat.json", "precedence": "reviewed_explicit", @@ -246,11 +246,11 @@ ] } }, - "idempotency": "idempotent", + "idempotency": "unknown", "interface_mode": "composite", "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.", "reviewed": true, - "risk": "low", + "risk": "medium", "source_refs": [ "ShortcutRegistry:chat +at-me", "cobra-help:dws chat +at-me", @@ -259,7 +259,7 @@ "internal/cli/schema_hints/selection/chat.json" ], "use_when": [ - "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。" + "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。默认只读且不会发送、撤回或标记任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] }, "chat +bot-find": { @@ -3623,8 +3623,8 @@ "avoid_when": [ "要跨多个会话按关键词、发送者或消息类型检索时使用 +search-msg;已有一批精确消息 ID 时使用 +messages-mget" ], - "confirmation": "not_required", - "effect": "read", + "confirmation": "user_required", + "effect": "write", "effect_source": "agent-hint", "examples": [ "dws chat +chat-messages --group \u003copenConversationId\u003e --direction older", @@ -3652,14 +3652,14 @@ "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "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." + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -3684,34 +3684,34 @@ ] }, "confirmation": { - "value": "not_required", + "value": "user_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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { - "value": "not_required", + "value": "user_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." + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files." } ] }, "effect": { - "value": "read", + "value": "write", "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { - "value": "read", + "value": "write", "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." + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -3738,18 +3738,18 @@ ] }, "idempotency": { - "value": "idempotent", + "value": "unknown", "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { - "value": "idempotent", + "value": "unknown", "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." + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -3758,14 +3758,14 @@ "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "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." + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -3774,14 +3774,14 @@ "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "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." + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -3806,14 +3806,14 @@ "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "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." + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files." }, { "value": true, @@ -3825,24 +3825,24 @@ ] }, "risk": { - "value": "low", + "value": "medium", "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { - "value": "low", + "value": "medium", "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." + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files." } ] }, "use_when": { "value": [ - "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ], "source": "internal/cli/schema_hints/selection/chat.json", "precedence": "reviewed_explicit", @@ -3851,7 +3851,7 @@ "candidates": [ { "value": [ - "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ], "source": "internal/cli/schema_hints/selection/chat.json", "precedence": "reviewed_explicit", @@ -3861,11 +3861,11 @@ ] } }, - "idempotency": "idempotent", + "idempotency": "unknown", "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", + "risk": "medium", "source_refs": [ "ShortcutRegistry:chat +chat-messages", "cobra-help:dws chat +chat-messages", @@ -3874,7 +3874,7 @@ "internal/cli/schema_hints/selection/chat.json" ], "use_when": [ - "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] }, "chat +chat-mute": { @@ -9047,8 +9047,8 @@ "avoid_when": [ "需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令" ], - "confirmation": "not_required", - "effect": "read", + "confirmation": "user_required", + "effect": "write", "effect_source": "agent-hint", "examples": [ "dws chat +messages-mget --msg-ids msgId1,msgId2" @@ -9075,14 +9075,14 @@ "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { "value": "available", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity." + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -9107,34 +9107,34 @@ ] }, "confirmation": { - "value": "not_required", + "value": "user_required", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { - "value": "not_required", + "value": "user_required", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity." + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files." } ] }, "effect": { - "value": "read", + "value": "write", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { - "value": "read", + "value": "write", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity." + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -9159,18 +9159,18 @@ ] }, "idempotency": { - "value": "idempotent", + "value": "unknown", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { - "value": "idempotent", + "value": "unknown", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity." + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -9179,14 +9179,14 @@ "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { "value": "composite", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity." + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -9195,14 +9195,14 @@ "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { "value": "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.", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity." + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -9227,14 +9227,14 @@ "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { "value": true, "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity." + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files." }, { "value": true, @@ -9246,24 +9246,24 @@ ] }, "risk": { - "value": "low", + "value": "medium", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { - "value": "low", + "value": "medium", "source": "internal/cli/schema_hints/metadata/chat.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity." + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files." } ] }, "use_when": { "value": [ - "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" + "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ], "source": "internal/cli/schema_hints/selection/chat.json", "precedence": "reviewed_explicit", @@ -9272,7 +9272,7 @@ "candidates": [ { "value": [ - "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" + "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ], "source": "internal/cli/schema_hints/selection/chat.json", "precedence": "reviewed_explicit", @@ -9282,11 +9282,11 @@ ] } }, - "idempotency": "idempotent", + "idempotency": "unknown", "interface_mode": "composite", "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.", "reviewed": true, - "risk": "low", + "risk": "medium", "source_refs": [ "ShortcutRegistry:chat +messages-mget", "cobra-help:dws chat +messages-mget", @@ -9295,7 +9295,7 @@ "internal/cli/schema_hints/selection/chat.json" ], "use_when": [ - "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" + "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] }, "chat +messages-query-send-status": { @@ -11120,8 +11120,8 @@ "avoid_when": [ "只想读取一个已知会话的连续历史时使用 +chat-messages;已有精确消息 ID 时使用 +messages-mget" ], - "confirmation": "not_required", - "effect": "read", + "confirmation": "user_required", + "effect": "write", "effect_source": "agent-hint", "examples": [ "dws chat +search-msg --query \"周报\" --senders \u003copenDingTalkId\u003e --days 3 --page-all", @@ -11149,14 +11149,14 @@ "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "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." + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -11181,34 +11181,34 @@ ] }, "confirmation": { - "value": "not_required", + "value": "user_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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { - "value": "not_required", + "value": "user_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." + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files." } ] }, "effect": { - "value": "read", + "value": "write", "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { - "value": "read", + "value": "write", "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." + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -11235,18 +11235,18 @@ ] }, "idempotency": { - "value": "idempotent", + "value": "unknown", "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { - "value": "idempotent", + "value": "unknown", "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." + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -11255,14 +11255,14 @@ "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "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." + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -11271,14 +11271,14 @@ "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "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." + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -11303,14 +11303,14 @@ "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "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." + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files." }, { "value": true, @@ -11322,24 +11322,24 @@ ] }, "risk": { - "value": "low", + "value": "medium", "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { - "value": "low", + "value": "medium", "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." + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files." } ] }, "use_when": { "value": [ - "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。" + "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ], "source": "internal/cli/schema_hints/selection/chat.json", "precedence": "reviewed_explicit", @@ -11348,7 +11348,7 @@ "candidates": [ { "value": [ - "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。" + "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ], "source": "internal/cli/schema_hints/selection/chat.json", "precedence": "reviewed_explicit", @@ -11358,11 +11358,11 @@ ] } }, - "idempotency": "idempotent", + "idempotency": "unknown", "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", + "risk": "medium", "source_refs": [ "ShortcutRegistry:chat +search-msg", "cobra-help:dws chat +search-msg", @@ -11371,7 +11371,7 @@ "internal/cli/schema_hints/selection/chat.json" ], "use_when": [ - "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。" + "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] }, "chat +send-to-group": { @@ -11637,10 +11637,10 @@ "agent_summary_source": "dws-agent-selection/chat", "availability": "available", "avoid_when": [ - "要回复 Thread 或发送新回复时不要使用此只读入口;当前没有经过验证的 thread writer Shortcut" + "要回复 Thread 或发送新回复时不要使用此读取入口;当前没有经过验证的 thread writer Shortcut" ], - "confirmation": "not_required", - "effect": "read", + "confirmation": "user_required", + "effect": "write", "effect_source": "agent-hint", "examples": [ "dws chat +thread-replies --group \u003copenConversationId\u003e --thread-id \u003cthreadId\u003e", @@ -11668,20 +11668,20 @@ "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "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." + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files." } ] }, "avoid_when": { "value": [ - "要回复 Thread 或发送新回复时不要使用此只读入口;当前没有经过验证的 thread writer Shortcut" + "要回复 Thread 或发送新回复时不要使用此读取入口;当前没有经过验证的 thread writer Shortcut" ], "source": "internal/cli/schema_hints/selection/chat.json", "precedence": "reviewed_explicit", @@ -11690,7 +11690,7 @@ "candidates": [ { "value": [ - "要回复 Thread 或发送新回复时不要使用此只读入口;当前没有经过验证的 thread writer Shortcut" + "要回复 Thread 或发送新回复时不要使用此读取入口;当前没有经过验证的 thread writer Shortcut" ], "source": "internal/cli/schema_hints/selection/chat.json", "precedence": "reviewed_explicit", @@ -11700,34 +11700,34 @@ ] }, "confirmation": { - "value": "not_required", + "value": "user_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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { - "value": "not_required", + "value": "user_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." + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files." } ] }, "effect": { - "value": "read", + "value": "write", "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { - "value": "read", + "value": "write", "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." + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -11754,18 +11754,18 @@ ] }, "idempotency": { - "value": "idempotent", + "value": "unknown", "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { - "value": "idempotent", + "value": "unknown", "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." + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -11774,14 +11774,14 @@ "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "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." + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -11790,14 +11790,14 @@ "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "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." + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files." } ] }, @@ -11822,14 +11822,14 @@ "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "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." + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files." }, { "value": true, @@ -11841,24 +11841,24 @@ ] }, "risk": { - "value": "low", + "value": "medium", "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "candidates": [ { - "value": "low", + "value": "medium", "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." + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files." } ] }, "use_when": { "value": [ - "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ], "source": "internal/cli/schema_hints/selection/chat.json", "precedence": "reviewed_explicit", @@ -11867,7 +11867,7 @@ "candidates": [ { "value": [ - "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ], "source": "internal/cli/schema_hints/selection/chat.json", "precedence": "reviewed_explicit", @@ -11877,11 +11877,11 @@ ] } }, - "idempotency": "idempotent", + "idempotency": "unknown", "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", + "risk": "medium", "source_refs": [ "ShortcutRegistry:chat +thread-replies", "cobra-help:dws chat +thread-replies", @@ -11890,7 +11890,7 @@ "internal/cli/schema_hints/selection/chat.json" ], "use_when": [ - "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] }, "chat +unread-chats": { diff --git a/internal/cli/schema_agent_metadata/index.json b/internal/cli/schema_agent_metadata/index.json index 81debe8b..2cfdcd84 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:ee1c5251720a1cefb87e97f98ee15bca1f929179c9d9a1c598ea5e0c7e281f95", + "source_hash": "sha256:179a67a3fd47e2ef1786aaec197d52429a6522bf61a883df70f83febf334387b", "surface_hash": "sha256:698ccdc961c0b745e6524e33ae13d87ef4fcac012a2e833c7a727a79d35004db", "coverage": { "surface_products": 26, diff --git a/internal/cli/schema_agent_metadata_audit.json b/internal/cli/schema_agent_metadata_audit.json index d66234fe..320fdf9c 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:ee1c5251720a1cefb87e97f98ee15bca1f929179c9d9a1c598ea5e0c7e281f95", + "source_hash": "sha256:179a67a3fd47e2ef1786aaec197d52429a6522bf61a883df70f83febf334387b", "surface_hash": "sha256:698ccdc961c0b745e6524e33ae13d87ef4fcac012a2e833c7a727a79d35004db", "source_files": 160, "hint_files": 54, diff --git a/internal/cli/schema_catalog/catalog.json b/internal/cli/schema_catalog/catalog.json index 38f2006f..b3f2d70a 100644 --- a/internal/cli/schema_catalog/catalog.json +++ b/internal/cli/schema_catalog/catalog.json @@ -1,12 +1,12 @@ { "version": 1, "surface_hash": "sha256:698ccdc961c0b745e6524e33ae13d87ef4fcac012a2e833c7a727a79d35004db", - "source_hash": "sha256:7e52ceb70627216cc13cdbad03cb5daec57c03d03cc7f854ed06cadfa984e015", + "source_hash": "sha256:2336db101f882586f78322e88d8351ed8b80c9c66c6cb6268cfb7605c04077ed", "catalog": { "agent_metadata": { "products_with_metadata": 26, "source": "embedded-skill-metadata", - "source_hash": "sha256:ee1c5251720a1cefb87e97f98ee15bca1f929179c9d9a1c598ea5e0c7e281f95", + "source_hash": "sha256:179a67a3fd47e2ef1786aaec197d52429a6522bf61a883df70f83febf334387b", "surface_hash": "sha256:698ccdc961c0b745e6524e33ae13d87ef4fcac012a2e833c7a727a79d35004db", "surface_products": 26, "surface_tools": 818, @@ -9718,19 +9718,19 @@ "canonical_path": "chat.shortcut_at_me", "cli_name": "+at-me", "cli_path": "chat +at-me", - "confirmation": "not_required", - "description": "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。\n\n参数约束:\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", - "effect": "read", - "idempotency": "idempotent", + "confirmation": "user_required", + "description": "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。默认只读且不会发送、撤回或标记任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。\n\n参数约束:\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", + "effect": "write", + "idempotency": "unknown", "interface_mode": "composite", "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.", "name": "shortcut_at_me", "primary_cli_path": "chat +at-me", "reviewed": true, - "risk": "low", + "risk": "medium", "title": "查最近 @我 的消息(自动算时间窗,投影发送人/时间/内容/会话)", "use_when": [ - "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。" + "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。默认只读且不会发送、撤回或标记任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] }, { @@ -10082,19 +10082,19 @@ "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", + "confirmation": "user_required", + "description": "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。\n\n参数约束:\n - --group、--conversation-id、--id、--user、--open-dingtalk-id 必须且只能指定一个\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", + "effect": "write", + "idempotency": "unknown", "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", + "risk": "medium", "title": "拉取某个会话(群聊或单聊)的消息列表并投影出发言人/文本/时间", "use_when": [ - "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] }, { @@ -10628,19 +10628,19 @@ "canonical_path": "chat.shortcut_messages_mget", "cli_name": "+messages-mget", "cli_path": "chat +messages-mget", - "confirmation": "not_required", - "description": "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。\n\n参数约束:\n - --msg-ids 去重后必须包含 1-50 条消息 ID\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", - "effect": "read", - "idempotency": "idempotent", + "confirmation": "user_required", + "description": "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。\n\n参数约束:\n - --msg-ids 去重后必须包含 1-50 条消息 ID\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", + "effect": "write", + "idempotency": "unknown", "interface_mode": "composite", "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.", "name": "shortcut_messages_mget", "primary_cli_path": "chat +messages-mget", "reviewed": true, - "risk": "low", + "risk": "medium", "title": "根据消息 ID 批量查询消息(最多 50 条)", "use_when": [ - "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" + "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] }, { @@ -10836,19 +10836,19 @@ "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", + "confirmation": "user_required", + "description": "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。\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": "write", + "idempotency": "unknown", "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", + "risk": "medium", "title": "多维搜索消息,可全量翻页并批量富化详情", "use_when": [ - "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。" + "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] }, { @@ -10883,24 +10883,24 @@ "agent_summary_source": "dws-agent-selection/chat", "availability": "available", "avoid_when": [ - "要回复 Thread 或发送新回复时不要使用此只读入口;当前没有经过验证的 thread writer Shortcut" + "要回复 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", + "confirmation": "user_required", + "description": "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。\n\n参数约束:\n - --thread-id 与兼容参数 --topic-id 必须且只能指定一个\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", + "effect": "write", + "idempotency": "unknown", "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", + "risk": "medium", "title": "拉取某条话题消息的全部回复并投影出发言人/文本/时间", "use_when": [ - "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] }, { diff --git a/internal/cli/schema_catalog/tools/chat.json b/internal/cli/schema_catalog/tools/chat.json index 6e10ae9b..971c7634 100644 --- a/internal/cli/schema_catalog/tools/chat.json +++ b/internal/cli/schema_catalog/tools/chat.json @@ -50913,10 +50913,10 @@ "canonical_path": "chat.shortcut_at_me", "cli_name": "+at-me", "cli_path": "chat +at-me", - "confirmation": "not_required", - "description": "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。\n\n参数约束:\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", + "confirmation": "user_required", + "description": "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。默认只读且不会发送、撤回或标记任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。\n\n参数约束:\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", "display": "群聊 / 消息 / 机器人", - "effect": "read", + "effect": "write", "effect_source": "agent-hint", "examples": [ "dws chat +at-me", @@ -50943,7 +50943,7 @@ "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": "available" @@ -50951,7 +50951,7 @@ ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": "available" }, @@ -50995,17 +50995,17 @@ "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "not_required" + "value": "user_required" } ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "not_required" + "value": "user_required" }, "description": { "candidates": [ @@ -51013,29 +51013,29 @@ "precedence": "cobra_help", "selected": true, "source": "cobra_help", - "value": "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。\n\n参数约束:\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" + "value": "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。默认只读且不会发送、撤回或标记任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。\n\n参数约束:\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" } ], "precedence": "cobra_help", "resolution": "highest_precedence", "source": "cobra_help", - "value": "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。\n\n参数约束:\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" + "value": "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。默认只读且不会发送、撤回或标记任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。\n\n参数约束:\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" }, "effect": { "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "read" + "value": "write" } ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "read" + "value": "write" }, "examples": { "candidates": [ @@ -51063,23 +51063,23 @@ "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "idempotent" + "value": "unknown" } ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "idempotent" + "value": "unknown" }, "interface_mode": { "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": "composite" @@ -51087,7 +51087,7 @@ ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": "composite" }, @@ -51095,7 +51095,7 @@ "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": "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." @@ -51103,7 +51103,7 @@ ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": "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." }, @@ -51127,7 +51127,7 @@ "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": true @@ -51142,7 +51142,7 @@ ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": true }, @@ -51150,17 +51150,17 @@ "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "low" + "value": "medium" } ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "low" + "value": "medium" }, "title": { "candidates": [ @@ -51184,7 +51184,7 @@ "selected": true, "source": "internal/cli/schema_hints/selection/chat.json", "value": [ - "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。" + "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。默认只读且不会发送、撤回或标记任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] } ], @@ -51193,12 +51193,12 @@ "review_reason": "Agent-authored and reviewed from the built-in Shortcut intent, executable Cobra path, and declared outcome; it affects selection only and does not alter execution or safety facts.", "source": "internal/cli/schema_hints/selection/chat.json", "value": [ - "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。" + "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。默认只读且不会发送、撤回或标记任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] } }, "has_parameters": true, - "idempotency": "idempotent", + "idempotency": "unknown", "interface_mode": "composite", "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, @@ -51838,11 +51838,11 @@ "primary_cli_path": "chat +at-me", "product_id": "chat", "reviewed": true, - "risk": "low", + "risk": "medium", "source": "reviewed_command_registry", "title": "查最近 @我 的消息(自动算时间窗,投影发送人/时间/内容/会话)", "use_when": [ - "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。" + "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。默认只读且不会发送、撤回或标记任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] }, "chat.shortcut_bot_find": { @@ -58204,7 +58204,7 @@ "canonical_path": "chat.shortcut_chat_messages", "cli_name": "+chat-messages", "cli_path": "chat +chat-messages", - "confirmation": "not_required", + "confirmation": "user_required", "constraints": { "mutually_exclusive": [ [ @@ -58221,9 +58221,9 @@ ] ] }, - "description": "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。\n\n参数约束:\n - --group、--conversation-id、--id、--user、--open-dingtalk-id 必须且只能指定一个\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", + "description": "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。\n\n参数约束:\n - --group、--conversation-id、--id、--user、--open-dingtalk-id 必须且只能指定一个\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", "display": "群聊 / 消息 / 机器人", - "effect": "read", + "effect": "write", "effect_source": "agent-hint", "examples": [ "dws chat +chat-messages --group \u003copenConversationId\u003e --direction older", @@ -58250,7 +58250,7 @@ "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": "available" @@ -58258,7 +58258,7 @@ ], "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": "available" }, @@ -58302,17 +58302,17 @@ "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "not_required" + "value": "user_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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "not_required" + "value": "user_required" }, "description": { "candidates": [ @@ -58320,29 +58320,29 @@ "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 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" + "value": "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。\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 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" + "value": "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。\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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "read" + "value": "write" } ], "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "read" + "value": "write" }, "examples": { "candidates": [ @@ -58370,23 +58370,23 @@ "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "idempotent" + "value": "unknown" } ], "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "idempotent" + "value": "unknown" }, "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": "composite" @@ -58394,7 +58394,7 @@ ], "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": "composite" }, @@ -58402,7 +58402,7 @@ "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "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." @@ -58410,7 +58410,7 @@ ], "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "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." }, @@ -58434,7 +58434,7 @@ "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": true @@ -58449,7 +58449,7 @@ ], "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": true }, @@ -58457,17 +58457,17 @@ "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "low" + "value": "medium" } ], "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "low" + "value": "medium" }, "title": { "candidates": [ @@ -58491,7 +58491,7 @@ "selected": true, "source": "internal/cli/schema_hints/selection/chat.json", "value": [ - "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] } ], @@ -58500,12 +58500,12 @@ "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 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] } }, "has_parameters": true, - "idempotency": "idempotent", + "idempotency": "unknown", "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, @@ -59424,11 +59424,11 @@ "primary_cli_path": "chat +chat-messages", "product_id": "chat", "reviewed": true, - "risk": "low", + "risk": "medium", "source": "reviewed_command_registry", "title": "拉取某个会话(群聊或单聊)的消息列表并投影出发言人/文本/时间", "use_when": [ - "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] }, "chat.shortcut_chat_mute": { @@ -70451,10 +70451,10 @@ "canonical_path": "chat.shortcut_messages_mget", "cli_name": "+messages-mget", "cli_path": "chat +messages-mget", - "confirmation": "not_required", - "description": "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。\n\n参数约束:\n - --msg-ids 去重后必须包含 1-50 条消息 ID\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", + "confirmation": "user_required", + "description": "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。\n\n参数约束:\n - --msg-ids 去重后必须包含 1-50 条消息 ID\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", "display": "群聊 / 消息 / 机器人", - "effect": "read", + "effect": "write", "effect_source": "agent-hint", "examples": [ "dws chat +messages-mget --msg-ids msgId1,msgId2" @@ -70480,7 +70480,7 @@ "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": "available" @@ -70488,7 +70488,7 @@ ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": "available" }, @@ -70532,17 +70532,17 @@ "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "not_required" + "value": "user_required" } ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "not_required" + "value": "user_required" }, "description": { "candidates": [ @@ -70550,29 +70550,29 @@ "precedence": "cobra_help", "selected": true, "source": "cobra_help", - "value": "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。\n\n参数约束:\n - --msg-ids 去重后必须包含 1-50 条消息 ID\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" + "value": "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。\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/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。\n\n参数约束:\n - --msg-ids 去重后必须包含 1-50 条消息 ID\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" + "value": "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。\n\n参数约束:\n - --msg-ids 去重后必须包含 1-50 条消息 ID\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" }, "effect": { "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "read" + "value": "write" } ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "read" + "value": "write" }, "examples": { "candidates": [ @@ -70598,23 +70598,23 @@ "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "idempotent" + "value": "unknown" } ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "idempotent" + "value": "unknown" }, "interface_mode": { "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": "composite" @@ -70622,7 +70622,7 @@ ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": "composite" }, @@ -70630,7 +70630,7 @@ "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": "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." @@ -70638,7 +70638,7 @@ ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": "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." }, @@ -70662,7 +70662,7 @@ "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": true @@ -70677,7 +70677,7 @@ ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": true }, @@ -70685,17 +70685,17 @@ "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "low" + "value": "medium" } ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "low" + "value": "medium" }, "title": { "candidates": [ @@ -70719,7 +70719,7 @@ "selected": true, "source": "internal/cli/schema_hints/selection/chat.json", "value": [ - "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" + "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] } ], @@ -70728,12 +70728,12 @@ "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/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" + "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] } }, "has_parameters": true, - "idempotency": "idempotent", + "idempotency": "unknown", "interface_mode": "composite", "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, @@ -71196,11 +71196,11 @@ "primary_cli_path": "chat +messages-mget", "product_id": "chat", "reviewed": true, - "risk": "low", + "risk": "medium", "source": "reviewed_command_registry", "title": "根据消息 ID 批量查询消息(最多 50 条)", "use_when": [ - "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" + "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] }, "chat.shortcut_messages_query_send_status": { @@ -77489,7 +77489,7 @@ "canonical_path": "chat.shortcut_search_msg", "cli_name": "+search-msg", "cli_path": "chat +search-msg", - "confirmation": "not_required", + "confirmation": "user_required", "constraints": { "mutually_exclusive": [ [ @@ -77535,9 +77535,9 @@ ] ] }, - "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 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", + "description": "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。\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": "write", "effect_source": "agent-hint", "examples": [ "dws chat +search-msg --query \"周报\" --senders \u003copenDingTalkId\u003e --days 3 --page-all", @@ -77564,7 +77564,7 @@ "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": "available" @@ -77572,7 +77572,7 @@ ], "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": "available" }, @@ -77616,17 +77616,17 @@ "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "not_required" + "value": "user_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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "not_required" + "value": "user_required" }, "description": { "candidates": [ @@ -77634,29 +77634,29 @@ "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 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" + "value": "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。\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 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" + "value": "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。\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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "read" + "value": "write" } ], "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "read" + "value": "write" }, "examples": { "candidates": [ @@ -77684,23 +77684,23 @@ "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "idempotent" + "value": "unknown" } ], "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "idempotent" + "value": "unknown" }, "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": "composite" @@ -77708,7 +77708,7 @@ ], "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": "composite" }, @@ -77716,7 +77716,7 @@ "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "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." @@ -77724,7 +77724,7 @@ ], "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "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." }, @@ -77748,7 +77748,7 @@ "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": true @@ -77763,7 +77763,7 @@ ], "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": true }, @@ -77771,17 +77771,17 @@ "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "low" + "value": "medium" } ], "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "low" + "value": "medium" }, "title": { "candidates": [ @@ -77805,7 +77805,7 @@ "selected": true, "source": "internal/cli/schema_hints/selection/chat.json", "value": [ - "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。" + "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] } ], @@ -77814,12 +77814,12 @@ "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,绝不把截断结果标成完整。" + "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] } }, "has_parameters": true, - "idempotency": "idempotent", + "idempotency": "unknown", "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, @@ -80266,11 +80266,11 @@ "primary_cli_path": "chat +search-msg", "product_id": "chat", "reviewed": true, - "risk": "low", + "risk": "medium", "source": "reviewed_command_registry", "title": "多维搜索消息,可全量翻页并批量富化详情", "use_when": [ - "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。" + "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] }, "chat.shortcut_send_to_group": { @@ -80888,12 +80888,12 @@ "agent_summary_source": "dws-agent-selection/chat", "availability": "available", "avoid_when": [ - "要回复 Thread 或发送新回复时不要使用此只读入口;当前没有经过验证的 thread writer Shortcut" + "要回复 Thread 或发送新回复时不要使用此读取入口;当前没有经过验证的 thread writer Shortcut" ], "canonical_path": "chat.shortcut_thread_replies", "cli_name": "+thread-replies", "cli_path": "chat +thread-replies", - "confirmation": "not_required", + "confirmation": "user_required", "constraints": { "mutually_exclusive": [ [ @@ -80908,9 +80908,9 @@ ] ] }, - "description": "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。\n\n参数约束:\n - --thread-id 与兼容参数 --topic-id 必须且只能指定一个\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", + "description": "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。\n\n参数约束:\n - --thread-id 与兼容参数 --topic-id 必须且只能指定一个\n - --output-dir 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸", "display": "群聊 / 消息 / 机器人", - "effect": "read", + "effect": "write", "effect_source": "agent-hint", "examples": [ "dws chat +thread-replies --group \u003copenConversationId\u003e --thread-id \u003cthreadId\u003e", @@ -80937,7 +80937,7 @@ "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": "available" @@ -80945,7 +80945,7 @@ ], "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": "available" }, @@ -80957,7 +80957,7 @@ "selected": true, "source": "internal/cli/schema_hints/selection/chat.json", "value": [ - "要回复 Thread 或发送新回复时不要使用此只读入口;当前没有经过验证的 thread writer Shortcut" + "要回复 Thread 或发送新回复时不要使用此读取入口;当前没有经过验证的 thread writer Shortcut" ] } ], @@ -80966,7 +80966,7 @@ "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" + "要回复 Thread 或发送新回复时不要使用此读取入口;当前没有经过验证的 thread writer Shortcut" ] }, "canonical_path": { @@ -80989,17 +80989,17 @@ "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "not_required" + "value": "user_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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "not_required" + "value": "user_required" }, "description": { "candidates": [ @@ -81007,29 +81007,29 @@ "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 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" + "value": "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。\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 必须是工作目录内的相对路径,不允许绝对路径或 .. 逃逸" + "value": "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。\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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "read" + "value": "write" } ], "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "read" + "value": "write" }, "examples": { "candidates": [ @@ -81057,23 +81057,23 @@ "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "idempotent" + "value": "unknown" } ], "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "idempotent" + "value": "unknown" }, "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": "composite" @@ -81081,7 +81081,7 @@ ], "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": "composite" }, @@ -81089,7 +81089,7 @@ "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "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." @@ -81097,7 +81097,7 @@ ], "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "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." }, @@ -81121,7 +81121,7 @@ "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", "value": true @@ -81136,7 +81136,7 @@ ], "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", "value": true }, @@ -81144,17 +81144,17 @@ "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "selected": true, "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "low" + "value": "medium" } ], "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "source": "internal/cli/schema_hints/metadata/chat.json", - "value": "low" + "value": "medium" }, "title": { "candidates": [ @@ -81178,7 +81178,7 @@ "selected": true, "source": "internal/cli/schema_hints/selection/chat.json", "value": [ - "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] } ], @@ -81187,12 +81187,12 @@ "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 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] } }, "has_parameters": true, - "idempotency": "idempotent", + "idempotency": "unknown", "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, @@ -82003,11 +82003,11 @@ "primary_cli_path": "chat +thread-replies", "product_id": "chat", "reviewed": true, - "risk": "low", + "risk": "medium", "source": "reviewed_command_registry", "title": "拉取某条话题消息的全部回复并投影出发言人/文本/时间", "use_when": [ - "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ] }, "chat.shortcut_unread_chats": { diff --git a/internal/cli/schema_hints/metadata/chat.json b/internal/cli/schema_hints/metadata/chat.json index 6d11c8bc..7a0ec2f8 100644 --- a/internal/cli/schema_hints/metadata/chat.json +++ b/internal/cli/schema_hints/metadata/chat.json @@ -1153,17 +1153,17 @@ "runtime_gate": "none" }, "chat.shortcut_chat_messages": { - "effect": "read", - "risk": "low", - "confirmation": "not_required", - "idempotency": "idempotent", + "effect": "write", + "risk": "medium", + "confirmation": "user_required", + "idempotency": "unknown", "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.", + "review_reason": "Reviewed against the executable Shortcut, group/direct lower read tools, optional local-file writes, shared safe-output validation, resource de-duplication, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "cli_path": "chat +chat-messages", - "runtime_gate": "none" + "runtime_gate": "typed_yes" }, "chat.shortcut_messages_send": { "effect": "write", @@ -1231,17 +1231,17 @@ "runtime_gate": "none" }, "chat.shortcut_messages_mget": { - "effect": "read", - "risk": "low", - "confirmation": "not_required", - "idempotency": "idempotent", + "effect": "write", + "risk": "medium", + "confirmation": "user_required", + "idempotency": "unknown", "interface_mode": "composite", "availability": "available", "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.", "reviewed": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "cli_path": "chat +messages-mget", - "runtime_gate": "none" + "runtime_gate": "typed_yes" }, "chat.shortcut_messages_query_send_status": { "effect": "read", @@ -1296,17 +1296,17 @@ "runtime_gate": "none" }, "chat.shortcut_at_me": { - "effect": "read", - "risk": "low", - "confirmation": "not_required", - "idempotency": "idempotent", + "effect": "write", + "risk": "medium", + "confirmation": "user_required", + "idempotency": "unknown", "interface_mode": "composite", "availability": "available", "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.", "reviewed": true, - "review_reason": "Reviewed against the built-in Shortcut registry and mounted Cobra command: publishes the executable risk gate and composite interface without inventing a direct MCP identity.", + "review_reason": "Reviewed against the built-in Shortcut registry, mounted Cobra command, and optional local-file writes; the static runtime risk is conservatively write because --download-resources writes files.", "cli_path": "chat +at-me", - "runtime_gate": "none" + "runtime_gate": "typed_yes" }, "chat.shortcut_broadcast": { "effect": "write", @@ -1361,17 +1361,17 @@ "runtime_gate": "none" }, "chat.shortcut_search_msg": { - "effect": "read", - "risk": "low", - "confirmation": "not_required", - "idempotency": "idempotent", + "effect": "write", + "risk": "medium", + "confirmation": "user_required", + "idempotency": "unknown", "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.", + "review_reason": "Reviewed against the executable advanced-search flags, page and enrichment failure ledgers, optional local-file writes, shared safe-output validation, and per-resource download ledger; the static runtime risk is conservatively write because --download-resources writes files.", "cli_path": "chat +search-msg", - "runtime_gate": "none" + "runtime_gate": "typed_yes" }, "chat.shortcut_send_to_group": { "effect": "write", @@ -1387,17 +1387,17 @@ "runtime_gate": "typed_yes" }, "chat.shortcut_thread_replies": { - "effect": "read", - "risk": "low", - "confirmation": "not_required", - "idempotency": "idempotent", + "effect": "write", + "risk": "medium", + "confirmation": "user_required", + "idempotency": "unknown", "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.", + "review_reason": "Reviewed against the executable thread/topic alias contract, real list_topic_replies mapping, optional local-file writes, group fallback for resource context, and per-resource failure ledger; the static runtime risk is conservatively write because --download-resources writes files.", "cli_path": "chat +thread-replies", - "runtime_gate": "none" + "runtime_gate": "typed_yes" }, "chat.shortcut_unread_chats": { "effect": "read", diff --git a/internal/cli/schema_hints/selection/chat.json b/internal/cli/schema_hints/selection/chat.json index 24066d7f..d84a884e 100644 --- a/internal/cli/schema_hints/selection/chat.json +++ b/internal/cli/schema_hints/selection/chat.json @@ -2113,7 +2113,7 @@ "chat.shortcut_chat_messages": { "agent_summary": "拉取某个会话(群聊或单聊)的消息列表并投影出发言人/文本/时间", "use_when": [ - "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + "当你想快速看某个会话里的消息(谁在什么时间说了什么),而不想拿到一大坨原始消息字段时使用;群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ], "avoid_when": [ "要跨多个会话按关键词、发送者或消息类型检索时使用 +search-msg;已有一批精确消息 ID 时使用 +messages-mget" @@ -2230,7 +2230,7 @@ "chat.shortcut_messages_mget": { "agent_summary": "根据消息 ID 批量查询消息(最多 50 条)", "use_when": [ - "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。" + "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ], "avoid_when": [ "需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令" @@ -2325,7 +2325,7 @@ "chat.shortcut_at_me": { "agent_summary": "查最近 @我 的消息(自动算时间窗,投影发送人/时间/内容/会话)", "use_when": [ - "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。" + "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息,再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。默认只读且不会发送、撤回或标记任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ], "avoid_when": [ "需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令" @@ -2422,7 +2422,7 @@ "chat.shortcut_search_msg": { "agent_summary": "多维搜索消息,可全量翻页并批量富化详情", "use_when": [ - "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。" + "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天,也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ], "avoid_when": [ "只想读取一个已知会话的连续历史时使用 +chat-messages;已有精确消息 ID 时使用 +messages-mget" @@ -2461,10 +2461,10 @@ "chat.shortcut_thread_replies": { "agent_summary": "拉取某条话题消息的全部回复并投影出发言人/文本/时间", "use_when": [ - "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。" + "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么),而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表,可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。" ], "avoid_when": [ - "要回复 Thread 或发送新回复时不要使用此只读入口;当前没有经过验证的 thread writer Shortcut" + "要回复 Thread 或发送新回复时不要使用此读取入口;当前没有经过验证的 thread writer Shortcut" ], "examples": [ "dws chat +thread-replies --group --thread-id ", diff --git a/internal/helpers/chat.go b/internal/helpers/chat.go index 5a17af9a..ef6ea2a9 100644 --- a/internal/helpers/chat.go +++ b/internal/helpers/chat.go @@ -13,6 +13,7 @@ import ( "strconv" "strings" "time" + "unicode" "unicode/utf8" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli" @@ -289,6 +290,47 @@ func normalizeAtPlaceholders(text string, ids []string, wrapAngle bool) string { return text } +// NormalizeMessageMentions applies the placeholder convention required by the +// selected sender identity and ensures a declared @all is present in the body. +// Current-user messages use <@id>/<@all>; bot and webhook messages use +// @id/@mobile/@all. +func NormalizeMessageMentions(text string, ids []string, atAll, wrapAngle bool) string { + text = normalizeAtPlaceholders(text, ids, wrapAngle) + allPlaceholder := "@all" + if wrapAngle { + text = normalizeAtPlaceholders(text, []string{"all"}, true) + allPlaceholder = "<@all>" + } else { + text = strings.ReplaceAll(text, "<@all>", "@all") + } + if atAll && !containsMessageMention(text, allPlaceholder) { + text = allPlaceholder + " " + text + } + return text +} + +func containsMessageMention(text, placeholder string) bool { + if strings.HasPrefix(placeholder, "<") { + return strings.Contains(text, placeholder) + } + for searchFrom := 0; ; { + offset := strings.Index(text[searchFrom:], placeholder) + if offset < 0 { + return false + } + end := searchFrom + offset + len(placeholder) + if end == len(text) { + return true + } + next, _ := utf8.DecodeRuneInString(text[end:]) + if !unicode.IsLetter(next) && !unicode.IsDigit(next) && + next != '_' && next != '-' { + return true + } + searchFrom = end + } +} + func resolveOpenDingTalkID(ctx context.Context, value string) (string, error) { ids, err := resolveOpenDingTalkIDs(ctx, []string{value}) if err != nil { diff --git a/internal/helpers/chat_coverage_more_test.go b/internal/helpers/chat_coverage_more_test.go index 8a72c36f..a16ac2cb 100644 --- a/internal/helpers/chat_coverage_more_test.go +++ b/internal/helpers/chat_coverage_more_test.go @@ -114,6 +114,18 @@ func TestCrossPlatformCoverageChatDirectionAndScalarCoverage(t *testing.T) { for _, wrap := range []bool{true, false} { _ = normalizeAtPlaceholders("hello @u1 <@u2>", []string{"", "u1", "u2"}, wrap) } + if got := NormalizeMessageMentions("hello @u1", []string{"u1"}, true, true); got != "<@all> hello <@u1>" { + t.Fatalf("current-user mention normalization = %q", got) + } + if got := NormalizeMessageMentions("<@all> <@u1>", []string{"u1"}, true, false); got != "@all @u1" { + t.Fatalf("bot mention normalization = %q", got) + } + if got := NormalizeMessageMentions("@alliance hello", nil, true, false); got != "@all @alliance hello" { + t.Fatalf("bot @all token detection = %q", got) + } + if got := NormalizeMessageMentions("hello @all", nil, true, false); got != "hello @all" { + t.Fatalf("trailing bot @all detection = %q", got) + } } func TestCrossPlatformCoverageChatContactMappingCoverage(t *testing.T) { diff --git a/internal/shortcut/chat/chat_message.go b/internal/shortcut/chat/chat_message.go index ed371b9f..b4ee2458 100644 --- a/internal/shortcut/chat/chat_message.go +++ b/internal/shortcut/chat/chat_message.go @@ -526,8 +526,8 @@ var MessagesMget = shortcut.Shortcut{ Command: "+messages-mget", Product: "im", Description: "根据消息 ID 批量查询消息(最多 50 条)", - Intent: "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。", - Risk: shortcut.RiskRead, + Intent: "当你已有一批消息 openMsgId、需要批量取回完整详情、reaction 和可执行资源引用时使用;一次最多 50 条。--download-resources 可把所有可识别 mediaId/fileId 安全下载到工作目录内,并逐资源返回成功/失败 ledger。默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。", + Risk: shortcut.RiskWrite, 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(默认输出)"}, @@ -620,7 +620,7 @@ func DownloadMessageResources( ) map[string]any { resources := make([]map[string]any, 0) for _, message := range messages { - resources = append(resources, chatmsg.Resources(message)...) + resources = append(resources, chatmsg.ResourcesDeep(message)...) } discoveredCount := len(resources) uniqueResources := make([]map[string]any, 0, len(resources)) @@ -635,6 +635,10 @@ func DownloadMessageResources( messageID = "" } key := strings.ToLower(resourceType) + "\x00" + resourceID + if strings.EqualFold(resourceType, "mediaId") { + conversationID := strings.TrimSpace(fmt.Sprint(arguments["open-conversation-id"])) + key += "\x00" + messageID + "\x00" + conversationID + } if resourceID != "" && resourceID != "" { if seen[key] { continue diff --git a/internal/shortcut/chat/chat_message_coverage_test.go b/internal/shortcut/chat/chat_message_coverage_test.go index c4539e1d..255c359d 100644 --- a/internal/shortcut/chat/chat_message_coverage_test.go +++ b/internal/shortcut/chat/chat_message_coverage_test.go @@ -39,10 +39,10 @@ func TestCrossPlatformCoverageListMessageRichProjection(t *testing.T) { } func TestCrossPlatformCoverageMgetResourceDownloadOutcomes(t *testing.T) { - baseArgs := []string{"chat", "+messages-mget", "--msg-ids", "msg", "--download-resources"} + baseArgs := []string{"chat", "+messages-mget", "--msg-ids", "msg", "--download-resources", "--yes"} readyMget := `{"result":[{"openMessageId":"msg","openConversationId":"cid","content":"{\"mediaId\":\"@file\"}"}]}` missingContextMget := `{"result":[{"content":"{\"mediaId\":\"@file\"}"}]}` - validInfo := `{"result":{"resourceUrl":"https://example.test/resource.bin"}}` + validInfo := `{"result":{"resourceUrl":"https://download.dingtalk.com/resource.bin"}}` t.Run("dry run", func(t *testing.T) { helpers.InitDeps(&larkAlignmentCaller{responses: map[string]string{ diff --git a/internal/shortcut/chat/coverage_completion_test.go b/internal/shortcut/chat/coverage_completion_test.go index 06708738..6d3d34b2 100644 --- a/internal/shortcut/chat/coverage_completion_test.go +++ b/internal/shortcut/chat/coverage_completion_test.go @@ -234,7 +234,7 @@ func TestCrossPlatformCoverageFlagAndMgetValidation(t *testing.T) { fake := &larkAlignmentCaller{failProductTool: "im/list_messages_by_ids"} helpers.InitDeps(fake) root := newPlatformCoverageRoot() - root.SetArgs([]string{"chat", "+messages-mget", "--msg-ids", "msg"}) + root.SetArgs([]string{"chat", "+messages-mget", "--msg-ids", "msg", "--yes"}) if err := root.Execute(); err == nil { t.Fatal("mget lower error was swallowed") } diff --git a/internal/shortcut/chat/gap_fill_test.go b/internal/shortcut/chat/gap_fill_test.go index e81275eb..82581797 100644 --- a/internal/shortcut/chat/gap_fill_test.go +++ b/internal/shortcut/chat/gap_fill_test.go @@ -463,7 +463,7 @@ func TestCrossPlatformCoverageFindCardBizIDResponseShapes(t *testing.T) { } } -func TestCrossPlatformCoverageMessageResourceDownloadDeduplicatesAndUsesFallbackConversation(t *testing.T) { +func TestCrossPlatformCoverageMessageResourceDownloadKeepsNestedMessageContext(t *testing.T) { resetResourceDownloadHooks(t) t.Chdir(t.TempDir()) resourceDownload = func( @@ -480,12 +480,16 @@ func TestCrossPlatformCoverageMessageResourceDownloadDeduplicatesAndUsesFallback return 7, nil } fake := &larkAlignmentCaller{responses: map[string]string{ - "im/get_resource_download_url": `{"result":{"resourceUrl":"https://example.test/resource.bin"}}`, + "im/get_resource_download_url": `{"result":{"resourceUrl":"https://download.dingtalk.com/resource.bin"}}`, }} helpers.InitDeps(fake) message := map[string]any{ "openMessageId": "msg", "content": `{"mediaId":"@same","nested":{"mediaId":"@same"}}`, + "quotedMessage": map[string]any{ + "openMessageId": "msg-quoted", + "content": `{"mediaId":"@quoted"}`, + }, } secondMessage := map[string]any{ "openMessageId": "msg-2", @@ -497,7 +501,7 @@ func TestCrossPlatformCoverageMessageResourceDownloadDeduplicatesAndUsesFallback Command: "+gap-resource-download", Flags: MessageResourceDownloadFlags(), Execute: func(rt *shortcut.RuntimeContext) error { - ledger = DownloadMessageResources(rt, []map[string]any{message, secondMessage}, "cid-fallback") + ledger = DownloadMessageResources(rt, []map[string]any{message, message, secondMessage}, "cid-fallback") return nil }, }) @@ -506,13 +510,25 @@ func TestCrossPlatformCoverageMessageResourceDownloadDeduplicatesAndUsesFallback if err := root.Execute(); err != nil { t.Fatal(err) } - if ledger["discoveredCount"] != 2 || ledger["requestedCount"] != 1 || - ledger["deduplicatedCount"] != 1 || ledger["downloadedCount"] != 1 { + if ledger["discoveredCount"] != 5 || ledger["requestedCount"] != 3 || + ledger["deduplicatedCount"] != 2 || ledger["downloadedCount"] != 3 { t.Fatalf("download ledger = %#v", ledger) } - if len(fake.calls) != 1 || fake.calls[0].args["openConversationId"] != "cid-fallback" { + if len(fake.calls) != 3 { t.Fatalf("resource lookup = %#v", fake.calls) } + gotMessageIDs := map[string]bool{} + for _, call := range fake.calls { + if call.args["openConversationId"] != "cid-fallback" { + t.Errorf("resource conversation = %#v", call.args) + } + gotMessageIDs[call.args["openMessageId"].(string)] = true + } + for _, messageID := range []string{"msg", "msg-quoted", "msg-2"} { + if !gotMessageIDs[messageID] { + t.Errorf("missing resource lookup for %q: %#v", messageID, fake.calls) + } + } } func TestCrossPlatformCoverageMessageFileResourceDownloadUsesDriveAndPreservesName(t *testing.T) { @@ -532,7 +548,7 @@ func TestCrossPlatformCoverageMessageFileResourceDownloadUsesDriveAndPreservesNa return int64(len("drive-file")), nil } fake := &larkAlignmentCaller{responses: map[string]string{ - "drive/download_file": `{"result":{"downloadUrl":"https://example.test/opaque","fileName":"fixture.txt"}}`, + "drive/download_file": `{"result":{"downloadUrl":"https://download.dingtalk.com/opaque","fileName":"fixture.txt"}}`, }} helpers.InitDeps(fake) @@ -542,6 +558,7 @@ func TestCrossPlatformCoverageMessageFileResourceDownloadUsesDriveAndPreservesNa "--type", "fileId", "--resource-id", "drive-file", "--output", "./downloads/", + "--yes", }) if err := root.Execute(); err != nil { t.Fatal(err) @@ -601,7 +618,7 @@ func TestCrossPlatformCoverageMessageResourceDownloadDisambiguatesSameNames(t *t return 1, nil } helpers.InitDeps(&larkAlignmentCaller{responses: map[string]string{ - "drive/download_file": `{"result":{"downloadUrl":"https://example.test/opaque","fileName":"fixture.txt"}}`, + "drive/download_file": `{"result":{"downloadUrl":"https://download.dingtalk.com/opaque","fileName":"fixture.txt"}}`, }}) var ledger map[string]any diff --git a/internal/shortcut/chat/lark_alignment_test.go b/internal/shortcut/chat/lark_alignment_test.go index 5aeaf5f3..fda0c22e 100644 --- a/internal/shortcut/chat/lark_alignment_test.go +++ b/internal/shortcut/chat/lark_alignment_test.go @@ -117,27 +117,35 @@ func TestMessagesSendRoutesIdentitySpecificTransports(t *testing.T) { product string tool string want map[string]any + bodyKey string + body string }{ { name: "user", - args: []string{"chat", "+messages-send", "--as", "user", "--chat-id", "cid", "--markdown", "hello", "--idempotency-key", "u1", "--yes"}, + args: []string{"chat", "+messages-send", "--as", "user", "--chat-id", "cid", "--markdown", "hello @D1", "--at-open-dingtalk-ids", "D1", "--at-all", "--idempotency-key", "u1", "--yes"}, product: "chat", tool: "send_personal_message", want: map[string]any{"openConversationId": "cid", "msgType": "markdown", "uuid": "u1"}, + bodyKey: "content", + body: "<@all> hello <@D1>", }, { name: "bot", - args: []string{"chat", "+messages-send", "--identity", "bot", "--robot-code", "robot", "--group", "cid", "--text", "hello", "--at-all", "--yes"}, + args: []string{"chat", "+messages-send", "--identity", "bot", "--robot-code", "robot", "--group", "cid", "--text", "<@u1> hello", "--at-user-ids", "u1", "--at-all", "--yes"}, product: "bot", tool: "send_robot_group_message", want: map[string]any{"robotCode": "robot", "openConversationId": "cid", "isAtAll": "true"}, + bodyKey: "markdown", + body: "@all @u1 hello", }, { name: "webhook", - args: []string{"chat", "+messages-send", "--identity", "webhook", "--webhook-token", "token", "--text", "hello", "--at-all", "--yes"}, + args: []string{"chat", "+messages-send", "--identity", "webhook", "--webhook-token", "token", "--text", "<@13800000000> hello", "--at-mobiles", "13800000000", "--at-all", "--yes"}, product: "bot", tool: "send_message_by_custom_robot", want: map[string]any{"robotToken": "token", "isAtAll": true}, + bodyKey: "text", + body: "@all @13800000000 hello", }, } for _, tt := range tests { @@ -161,6 +169,17 @@ func TestMessagesSendRoutesIdentitySpecificTransports(t *testing.T) { t.Errorf("%s = %#v, want %#v", key, call.args[key], want) } } + if tt.bodyKey == "content" { + var content map[string]string + if err := json.Unmarshal([]byte(call.args[tt.bodyKey].(string)), &content); err != nil { + t.Fatal(err) + } + if content["text"] != tt.body { + t.Errorf("content text = %q, want %q", content["text"], tt.body) + } + } else if call.args[tt.bodyKey] != tt.body { + t.Errorf("%s = %#v, want %q", tt.bodyKey, call.args[tt.bodyKey], tt.body) + } }) } } diff --git a/internal/shortcut/chat/resource_download.go b/internal/shortcut/chat/resource_download.go index 0abd9423..dbdaba7d 100644 --- a/internal/shortcut/chat/resource_download.go +++ b/internal/shortcut/chat/resource_download.go @@ -18,6 +18,7 @@ import ( "errors" "fmt" "io" + "net" "net/http" "net/url" "os" @@ -58,7 +59,7 @@ var MessagesResourceDownload = shortcut.Shortcut{ Intent: "当你需要拿到消息里的实际图片、视频、语音或钉盘文件,而不只是资源 ID 时使用;" + "mediaId 用消息和会话身份换取下载地址,fileId 复用钉盘下载能力,再安全写入工作目录内的相对路径。" + "默认不覆盖已有文件,只有显式传 --overwrite 才覆盖。", - Risk: shortcut.RiskRead, + Risk: shortcut.RiskWrite, Flags: []shortcut.Flag{ {Name: "type", Type: shortcut.FlagString, Default: "mediaId", Desc: "资源类型", Enum: []string{"mediaId", "fileId"}}, {Name: "resource-id", Type: shortcut.FlagString, Desc: "消息中的 mediaId 或 fileId", Required: true}, @@ -233,10 +234,12 @@ func resourceDownloadInfo(data map[string]any) (string, map[string]string, error parsed.Scheme = "https" resourceURL = parsed.String() } - if parsed.Scheme != "https" { - return "", nil, apperrors.NewAPI( - "资源下载接口未返回合法的 HTTPS 下载地址") + parsed, err = validateResourceDownloadURL(resourceURL) + if err != nil { + return "", nil, apperrors.NewAPI(fmt.Sprintf( + "资源下载接口返回了不受信任的下载地址: %v", err)) } + resourceURL = parsed.String() headers := map[string]string{} if values, ok := data["headers"].(map[string]any); ok { @@ -263,7 +266,45 @@ func resourceDownloadPreferredName(data map[string]any) string { func isAliyunOSSHost(host string) bool { host = strings.ToLower(strings.TrimSuffix(strings.TrimSpace(host), ".")) - return host == "aliyuncs.com" || strings.HasSuffix(host, ".aliyuncs.com") + if !strings.HasSuffix(host, ".aliyuncs.com") { + return false + } + for _, label := range strings.Split(strings.TrimSuffix(host, ".aliyuncs.com"), ".") { + if (label == "oss" || strings.HasPrefix(label, "oss-")) && + !strings.Contains(label, "internal") { + return true + } + } + return false +} + +func validateResourceDownloadURL(rawURL string) (*url.URL, error) { + parsed, err := url.Parse(strings.TrimSpace(rawURL)) + if err != nil || + parsed.Scheme != "https" || + strings.TrimSpace(parsed.Host) == "" || + parsed.User != nil { + return nil, apperrors.NewValidation("资源下载地址必须是受信任域名上的 HTTPS URL") + } + host := strings.ToLower(strings.TrimSuffix(strings.TrimSpace(parsed.Hostname()), ".")) + if host == "" || net.ParseIP(host) != nil || !isResourceDownloadAllowedHost(host) { + return nil, apperrors.NewValidation(fmt.Sprintf( + "资源下载地址域名 %q 不属于受信任的钉钉或 OSS 域名", host)) + } + if port := parsed.Port(); port != "" && port != "443" { + return nil, apperrors.NewValidation("资源下载地址只允许使用 HTTPS 默认端口") + } + return parsed, nil +} + +func isResourceDownloadAllowedHost(host string) bool { + host = strings.ToLower(strings.TrimSuffix(strings.TrimSpace(host), ".")) + // Lower tools may return signed headers with the URL. Keep those headers + // confined to platform-owned public download families; extend this list + // only after observing another official production download host. + return isAliyunOSSHost(host) || + host == "dingtalk.com" || + strings.HasSuffix(host, ".dingtalk.com") } func resolveResourceDownloadPath( @@ -403,22 +444,34 @@ func downloadResourceAtomically( if client == nil { client = &http.Client{Timeout: resourceDownloadTimeout} } + parsedResourceURL, err := validateResourceDownloadURL(resourceURL) + if err != nil { + return 0, err + } clientCopy := *client client = &clientCopy originalRedirect := client.CheckRedirect client.CheckRedirect = func(req *http.Request, via []*http.Request) error { - if req.URL.Scheme != "https" || strings.TrimSpace(req.URL.Host) == "" { - return apperrors.NewValidation("资源下载重定向必须使用 HTTPS") + if _, redirectErr := validateResourceDownloadURL(req.URL.String()); redirectErr != nil { + return apperrors.NewValidation(fmt.Sprintf( + "资源下载重定向指向了不受信任的地址: %v", redirectErr)) } if len(via) >= 10 { return apperrors.NewAPI("资源下载重定向次数过多") } + if len(via) > 0 && + !strings.EqualFold(req.URL.Hostname(), via[len(via)-1].URL.Hostname()) { + for key := range headers { + req.Header.Del(key) + } + } if originalRedirect != nil { return originalRedirect(req, via) } return nil } - request, err := http.NewRequestWithContext(ctx, http.MethodGet, resourceURL, nil) + request, err := http.NewRequestWithContext( + ctx, http.MethodGet, parsedResourceURL.String(), nil) if err != nil { return 0, apperrors.NewValidation(fmt.Sprintf("创建资源下载请求失败: %v", err)) } diff --git a/internal/shortcut/chat/resource_download_coverage_test.go b/internal/shortcut/chat/resource_download_coverage_test.go index d6f7c2fb..5e7e50ee 100644 --- a/internal/shortcut/chat/resource_download_coverage_test.go +++ b/internal/shortcut/chat/resource_download_coverage_test.go @@ -9,7 +9,6 @@ import ( "errors" "io" "net/http" - "net/http/httptest" "os" "path/filepath" "strings" @@ -58,6 +57,7 @@ func TestCrossPlatformCoverageResourceDownloadCommandOutcomes(t *testing.T) { "--resource-id", "@image", "--message-id", "msg", "--open-conversation-id", "cid", + "--yes", } t.Run("dry run", func(t *testing.T) { helpers.InitDeps(&larkAlignmentCaller{}) @@ -89,7 +89,7 @@ func TestCrossPlatformCoverageResourceDownloadCommandOutcomes(t *testing.T) { resetResourceDownloadHooks(t) resourceGetwd = func() (string, error) { return "", errors.New("getwd") } helpers.InitDeps(&larkAlignmentCaller{responses: map[string]string{ - "im/get_resource_download_url": `{"result":{"resourceUrl":"https://example.test/file"}}`, + "im/get_resource_download_url": `{"result":{"resourceUrl":"https://download.dingtalk.com/file"}}`, }}) root := newPlatformCoverageRoot() root.SetArgs(baseArgs) @@ -101,7 +101,7 @@ func TestCrossPlatformCoverageResourceDownloadCommandOutcomes(t *testing.T) { resetResourceDownloadHooks(t) resourceAbs = func(string) (string, error) { return "", errors.New("path") } helpers.InitDeps(&larkAlignmentCaller{responses: map[string]string{ - "im/get_resource_download_url": `{"result":{"resourceUrl":"https://example.test/file"}}`, + "im/get_resource_download_url": `{"result":{"resourceUrl":"https://download.dingtalk.com/file"}}`, }}) root := newPlatformCoverageRoot() root.SetArgs(baseArgs) @@ -132,7 +132,7 @@ func TestCrossPlatformCoverageResourceDownloadCommandOutcomes(t *testing.T) { resetResourceDownloadHooks(t) resourceDownload = tc.download helpers.InitDeps(&larkAlignmentCaller{responses: map[string]string{ - "im/get_resource_download_url": `{"result":{"resourceUrl":"https://example.test/file"}}`, + "im/get_resource_download_url": `{"result":{"resourceUrl":"https://download.dingtalk.com/file"}}`, }}) root := newPlatformCoverageRoot() root.SetArgs(append(append([]string{}, baseArgs...), "--output", filepath.Join(t.TempDir(), "file"))) @@ -164,7 +164,7 @@ func TestCrossPlatformCoverageResourceDownloadValidationAndInfo(t *testing.T) { } } resourceURL, headers, err := resourceDownloadInfo(map[string]any{ - "resourceUrl": "https://example.test/file", + "resourceUrl": "https://download.dingtalk.com/file", "headers": map[string]any{ "": "ignored", "X-Count": 3, @@ -175,14 +175,29 @@ func TestCrossPlatformCoverageResourceDownloadValidationAndInfo(t *testing.T) { t.Fatalf("resource info = %q %#v %v", resourceURL, headers, err) } for host, want := range map[string]bool{ - "ALIYUNCS.COM.": true, - "bucket.aliyuncs.com": true, - "example.com": false, + "ALIYUNCS.COM.": false, + "bucket.aliyuncs.com": false, + "bucket.oss-cn-hangzhou.aliyuncs.com": true, + "bucket.oss-internal.aliyuncs.com": false, + "bucket.oss-cn-hangzhou-internal.aliyuncs.com": false, + "example.com": false, } { if got := isAliyunOSSHost(host); got != want { t.Errorf("isAliyunOSSHost(%q) = %v, want %v", host, got, want) } } + for host, want := range map[string]bool{ + "DINGTALK.COM.": true, + "download.dingtalk.com": true, + "bucket.oss-cn-hangzhou.aliyuncs.com": true, + "aliyuncs.com.evil.test": false, + "evildingtalk.com": false, + "download.example.invalid": false, + } { + if got := isResourceDownloadAllowedHost(host); got != want { + t.Errorf("isResourceDownloadAllowedHost(%q) = %v, want %v", host, got, want) + } + } } func TestCrossPlatformCoverageResourceDownloadPathErrors(t *testing.T) { @@ -375,63 +390,94 @@ func TestCrossPlatformCoverageDownloadResourceHTTPFailures(t *testing.T) { errorClient := &http.Client{Transport: resourceRoundTripper(func(*http.Request) (*http.Response, error) { return nil, errors.New("transport") })} - if _, err := downloadResourceAtomically(context.Background(), errorClient, "https://example.test/file", nil, dest, false); err == nil { + if _, err := downloadResourceAtomically(context.Background(), errorClient, "https://download.dingtalk.com/file", nil, dest, false); err == nil { t.Fatal("transport error was swallowed") } - if _, err := downloadResourceAtomically(context.Background(), resourceResponseClient(500, "", 0), "https://example.test/file", nil, dest, false); err == nil { + if _, err := downloadResourceAtomically(context.Background(), resourceResponseClient(500, "", 0), "https://download.dingtalk.com/file", nil, dest, false); err == nil { t.Fatal("HTTP 500 was accepted") } - if _, err := downloadResourceAtomically(context.Background(), resourceResponseClient(200, "x", 2), "https://example.test/file", nil, dest, false); err == nil { + if _, err := downloadResourceAtomically(context.Background(), resourceResponseClient(200, "x", 2), "https://download.dingtalk.com/file", nil, dest, false); err == nil { t.Fatal("content-length mismatch was accepted") } - - plain := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, request *http.Request) { - if request.Header.Get("X-Test") != "ok" { - t.Errorf("missing forwarded header") - } - _, _ = w.Write([]byte("body")) - })) - t.Cleanup(plain.Close) nilClientDest := filepath.Join(t.TempDir(), "nil-client") - if _, err := downloadResourceAtomically(context.Background(), nil, plain.URL, map[string]string{"X-Test": "ok"}, nilClientDest, true); err != nil { - t.Fatal(err) + if _, err := downloadResourceAtomically( + context.Background(), nil, "https://evil.example/file", + map[string]string{"X-Test": "ok"}, nilClientDest, true, + ); err == nil { + t.Fatal("nil-client path accepted an untrusted URL") } } func TestCrossPlatformCoverageDownloadResourceRedirectGuards(t *testing.T) { - httpRedirect := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - http.Redirect(w, &http.Request{}, "http://example.test/file", http.StatusFound) - })) - t.Cleanup(httpRedirect.Close) + httpRedirect := &http.Client{Transport: resourceRoundTripper(func(*http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: http.StatusFound, + Body: io.NopCloser(strings.NewReader("")), + Header: http.Header{"Location": []string{"http://evil.example/file"}}, + }, nil + })} if _, err := downloadResourceAtomically( - context.Background(), httpRedirect.Client(), httpRedirect.URL, nil, + context.Background(), httpRedirect, "https://download.dingtalk.com/start", nil, filepath.Join(t.TempDir(), "http-redirect"), false, ); err == nil { t.Fatal("HTTP redirect was accepted") } - var loop *httptest.Server - loop = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - http.Redirect(w, &http.Request{}, loop.URL, http.StatusFound) - })) - t.Cleanup(loop.Close) + loop := &http.Client{Transport: resourceRoundTripper(func(*http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: http.StatusFound, + Body: io.NopCloser(strings.NewReader("")), + Header: http.Header{"Location": []string{"https://download.dingtalk.com/loop"}}, + }, nil + })} if _, err := downloadResourceAtomically( - context.Background(), loop.Client(), loop.URL, nil, + context.Background(), loop, "https://download.dingtalk.com/loop", nil, filepath.Join(t.TempDir(), "loop"), false, ); err == nil { t.Fatal("redirect loop was accepted") } - original := loop.Client() + original := *loop original.CheckRedirect = func(*http.Request, []*http.Request) error { return errors.New("original redirect policy") } if _, err := downloadResourceAtomically( - context.Background(), original, loop.URL, nil, + context.Background(), &original, "https://download.dingtalk.com/loop", nil, filepath.Join(t.TempDir(), "original"), false, ); err == nil { t.Fatal("original redirect rejection was swallowed") } + + redirectCount := 0 + crossHost := &http.Client{Transport: resourceRoundTripper(func(request *http.Request) (*http.Response, error) { + redirectCount++ + if redirectCount == 1 { + if request.Header.Get("X-Resource-Token") != "secret" { + t.Fatal("initial signed header missing") + } + return &http.Response{ + StatusCode: http.StatusFound, + Body: io.NopCloser(strings.NewReader("")), + Header: http.Header{"Location": []string{"https://bucket.oss-cn-hangzhou.aliyuncs.com/final"}}, + }, nil + } + if request.Header.Get("X-Resource-Token") != "" { + t.Fatal("server-supplied header leaked across download domains") + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader("ok")), + ContentLength: 2, + Header: make(http.Header), + }, nil + })} + if _, err := downloadResourceAtomically( + context.Background(), crossHost, "https://download.dingtalk.com/start", + map[string]string{"X-Resource-Token": "secret"}, + filepath.Join(t.TempDir(), "cross-host"), false, + ); err != nil { + t.Fatal(err) + } } func TestCrossPlatformCoverageDownloadResourceFileFailures(t *testing.T) { @@ -439,7 +485,7 @@ func TestCrossPlatformCoverageDownloadResourceFileFailures(t *testing.T) { run := func(t *testing.T, overwrite bool) error { t.Helper() _, err := downloadResourceAtomically( - context.Background(), client, "https://example.test/file", nil, + context.Background(), client, "https://download.dingtalk.com/file", nil, filepath.Join(t.TempDir(), "resource"), overwrite) return err } @@ -516,7 +562,7 @@ func TestCrossPlatformCoverageDownloadResourceCopySuccessWithBuffer(t *testing.T dest := filepath.Join(t.TempDir(), "resource") if _, err := downloadResourceAtomically( context.Background(), resourceResponseClient(200, "ok", 2), - "https://example.test/file", nil, dest, true, + "https://download.dingtalk.com/file", nil, dest, true, ); err != nil { t.Fatal(err) } diff --git a/internal/shortcut/chat/resource_download_test.go b/internal/shortcut/chat/resource_download_test.go index 11dcbf3c..e45b74a6 100644 --- a/internal/shortcut/chat/resource_download_test.go +++ b/internal/shortcut/chat/resource_download_test.go @@ -15,18 +15,18 @@ package chat import ( "context" + "io" "net/http" - "net/http/httptest" "os" "path/filepath" "strings" "testing" ) -func TestResourceDownloadInfo(t *testing.T) { +func TestCrossPlatformCoverageResourceDownloadInfo(t *testing.T) { url, headers, err := resourceDownloadInfo(map[string]any{ "result": map[string]any{ - "resourceUrl": []any{"https://download.example.test/path/image.png"}, + "resourceUrl": []any{"https://download.dingtalk.com/path/image.png"}, "headers": map[string]any{ "X-Test": "value", }, @@ -35,7 +35,7 @@ func TestResourceDownloadInfo(t *testing.T) { if err != nil { t.Fatal(err) } - if url != "https://download.example.test/path/image.png" { + if url != "https://download.dingtalk.com/path/image.png" { t.Fatalf("url = %q", url) } if headers["X-Test"] != "value" { @@ -57,6 +57,19 @@ func TestResourceDownloadInfo(t *testing.T) { }); err == nil { t.Fatal("plain HTTP URL unexpectedly accepted") } + for _, resourceURL := range []string{ + "https://evil.example/file", + "https://aliyuncs.com.evil.example/file", + "https://127.0.0.1/file", + "https://user:secret@download.dingtalk.com/file", + "https://download.dingtalk.com:8443/file", + } { + if _, _, err := resourceDownloadInfo( + map[string]any{"resourceUrl": resourceURL}, + ); err == nil { + t.Fatalf("untrusted URL %q unexpectedly accepted", resourceURL) + } + } } func TestResolveResourceDownloadPath(t *testing.T) { @@ -115,16 +128,23 @@ func TestResolveResourceDownloadPathRejectsSymlinkParentBeforeCreatingOutside(t } } -func TestDownloadResourceAtomically(t *testing.T) { +func TestCrossPlatformCoverageDownloadResourceAtomically(t *testing.T) { const body = "verified download bytes" - server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - _, _ = w.Write([]byte(body)) - })) - t.Cleanup(server.Close) + client := &http.Client{Transport: resourceRoundTripper(func(request *http.Request) (*http.Response, error) { + if request.URL.String() != "https://download.dingtalk.com/resource.bin" { + t.Fatalf("request URL = %q", request.URL) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(body)), + ContentLength: int64(len(body)), + Header: make(http.Header), + }, nil + })} dest := filepath.Join(t.TempDir(), "resource.bin") size, err := downloadResourceAtomically( - context.Background(), server.Client(), server.URL, nil, dest, false) + context.Background(), client, "https://download.dingtalk.com/resource.bin", nil, dest, false) if err != nil { t.Fatal(err) } @@ -139,7 +159,7 @@ func TestDownloadResourceAtomically(t *testing.T) { t.Fatalf("body = %q", got) } if _, err := downloadResourceAtomically( - context.Background(), server.Client(), server.URL, nil, dest, false, + context.Background(), client, "https://download.dingtalk.com/resource.bin", nil, dest, false, ); err == nil || !strings.Contains(err.Error(), "已存在") { t.Fatalf("no-clobber error = %v", err) } diff --git a/internal/shortcut/chat/unified_send.go b/internal/shortcut/chat/unified_send.go index f8be9ca3..b4632001 100644 --- a/internal/shortcut/chat/unified_send.go +++ b/internal/shortcut/chat/unified_send.go @@ -191,6 +191,14 @@ func executeMessagesSend(rt *shortcut.RuntimeContext) error { if contentType == "file" || contentType == "audio" || contentType == "video" { return executeMessagesSendUserFile(rt, group, openID, contentType) } + if group != "" { + body = helpers.NormalizeMessageMentions( + body, + uniqueShortcutStrings(rt.StrSlice("at-open-dingtalk-ids")), + rt.Bool("at-all"), + true, + ) + } content, _ := json.Marshal(map[string]string{"title": title, "text": body}) params := rt.AddAIMessageTag(map[string]any{ "msgType": "markdown", @@ -212,6 +220,15 @@ func executeMessagesSend(rt *shortcut.RuntimeContext) error { } return executeUnifiedMessageWrite(rt, "chat", "send_personal_message", params) case "bot": + body = helpers.NormalizeMessageMentions( + body, + append( + uniqueShortcutStrings(rt.StrSlice("at-user-ids")), + uniqueShortcutStrings(rt.StrSlice("at-open-dingtalk-ids"))..., + ), + rt.Bool("at-all"), + false, + ) params := map[string]any{ "robotCode": rt.Str("robot-code"), "title": title, @@ -241,6 +258,15 @@ func executeMessagesSend(rt *shortcut.RuntimeContext) error { } return executeUnifiedMessageWrite(rt, "bot", "batch_send_robot_msg_to_users", params) case "webhook": + body = helpers.NormalizeMessageMentions( + body, + append( + uniqueShortcutStrings(rt.StrSlice("at-user-ids")), + uniqueShortcutStrings(rt.StrSlice("at-mobiles"))..., + ), + rt.Bool("at-all"), + false, + ) params := map[string]any{ "robotToken": rt.Str("webhook-token"), "title": title, diff --git a/internal/shortcut/chatmsg/chatmsg.go b/internal/shortcut/chatmsg/chatmsg.go index 8cc10355..3585b103 100644 --- a/internal/shortcut/chatmsg/chatmsg.go +++ b/internal/shortcut/chatmsg/chatmsg.go @@ -284,6 +284,44 @@ func Resources(m map[string]any) []map[string]any { return out } +// ResourcesDeep returns resources from a message and each nested quoted, +// replied-to or forwarded message. Every nested resource is projected from the +// child message that owns it, so its download arguments never reuse the parent +// message ID. A missing child conversation ID inherits the enclosing +// conversation because quoted and forwarded records often omit that duplicate +// field. +func ResourcesDeep(m map[string]any) []map[string]any { + return resourcesDeep(m, "", 0) +} + +const maxResourceMessageDepth = 32 + +func resourcesDeep(m map[string]any, inheritedConversationID string, depth int) []map[string]any { + if m == nil || depth > maxResourceMessageDepth { + return nil + } + conversationID := strings.TrimSpace(fmt.Sprint(ConversationID(m))) + if conversationID == "" || conversationID == "" { + conversationID = inheritedConversationID + } + owned := m + if ConversationID(m) == nil && conversationID != "" { + owned = make(map[string]any, len(m)+1) + for key, value := range m { + owned[key] = value + } + owned["openConversationId"] = conversationID + } + out := append([]map[string]any(nil), Resources(owned)...) + if depth == maxResourceMessageDepth { + return out + } + for _, child := range nestedMessageChildren(m) { + out = append(out, resourcesDeep(child, conversationID, depth+1)...) + } + return out +} + var mediaIDTextRE = regexp.MustCompile(`(?i)media[_-]?id\s*[:=]\s*["']?([^"'\s)\]}>,]+)`) var fileIDTextRE = regexp.MustCompile(`(?i)\bfile[_-]?id\s*[:=]\s*["']?([^"'\s)\]}>,]+)`) @@ -292,13 +330,16 @@ func collectResourceIDs(value any, targetKey string, textPattern *regexp.Regexp, 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)) + normalizedKey := normalizeMessageKey(key) if normalizedKey == targetKey || (normalizedKey == "resourceid" && strings.EqualFold(resourceType, targetKey)) { if id := resourceIDScalar(child); id != "" { *out = append(*out, id) } } + if isNestedMessageBoundaryKey(normalizedKey) { + continue + } collectResourceIDs(child, targetKey, textPattern, out) } case []any: @@ -327,6 +368,74 @@ func collectResourceIDs(value any, targetKey string, textPattern *regexp.Regexp, } } +func normalizeMessageKey(key string) string { + return strings.ToLower(strings.NewReplacer("_", "", "-", "").Replace(key)) +} + +func isNestedMessageBoundaryKey(key string) bool { + switch key { + case "quotedmessage", "replymessage", "quoted", "replytomessage", + "forwardmessages", "forwardedmessages", "forwarded": + return true + default: + return false + } +} + +func nestedMessageMaps(value any) []map[string]any { + switch typed := value.(type) { + case map[string]any: + return []map[string]any{typed} + case []map[string]any: + return typed + case []any: + out := make([]map[string]any, 0, len(typed)) + for _, item := range typed { + if child, ok := item.(map[string]any); ok { + out = append(out, child) + } + } + return out + case string: + var decoded any + if json.Unmarshal([]byte(strings.TrimSpace(typed)), &decoded) == nil { + return nestedMessageMaps(decoded) + } + } + return nil +} + +func nestedMessageChildren(value any) []map[string]any { + out := make([]map[string]any, 0) + switch typed := value.(type) { + case map[string]any: + for key, child := range typed { + if isNestedMessageBoundaryKey(normalizeMessageKey(key)) { + out = append(out, nestedMessageMaps(child)...) + continue + } + out = append(out, nestedMessageChildren(child)...) + } + case []any: + for _, child := range typed { + out = append(out, nestedMessageChildren(child)...) + } + case []map[string]any: + for _, child := range typed { + out = append(out, nestedMessageChildren(child)...) + } + case string: + trimmed := strings.TrimSpace(typed) + if strings.HasPrefix(trimmed, "{") || strings.HasPrefix(trimmed, "[") { + var decoded any + if json.Unmarshal([]byte(trimmed), &decoded) == nil { + out = append(out, nestedMessageChildren(decoded)...) + } + } + } + return out +} + func resourceIDScalar(value any) string { text, ok := value.(string) if !ok { diff --git a/internal/shortcut/chatmsg/chatmsg_test.go b/internal/shortcut/chatmsg/chatmsg_test.go index 68685fa7..520bb917 100644 --- a/internal/shortcut/chatmsg/chatmsg_test.go +++ b/internal/shortcut/chatmsg/chatmsg_test.go @@ -189,6 +189,111 @@ func TestQuotedMessageIsBoundedAndSemantic(t *testing.T) { } } +func TestCrossPlatformCoverageResourcesRespectNestedMessageOwnership(t *testing.T) { + message := map[string]any{ + "openMessageId": "parent", + "openConversationId": "cid-parent", + "content": map[string]any{"mediaId": "media-parent"}, + "quotedMessage": map[string]any{ + "openMessageId": "quoted", + "content": map[string]any{"mediaId": "media-quoted"}, + }, + "forwardMessages": []any{ + map[string]any{ + "openMessageId": "forwarded", + "openConversationId": "cid-forwarded", + "content": map[string]any{"mediaId": "media-forwarded"}, + }, + }, + } + owned := Resources(message) + if len(owned) != 1 || owned[0]["resourceId"] != "media-parent" { + t.Fatalf("parent resources crossed message boundary: %#v", owned) + } + + deep := ResourcesDeep(message) + if len(deep) != 3 { + t.Fatalf("deep resources = %#v", deep) + } + argumentsByResource := map[string]map[string]any{} + for _, resource := range deep { + download := resource["download"].(map[string]any) + argumentsByResource[resource["resourceId"].(string)] = download["arguments"].(map[string]any) + } + for resourceID, want := range map[string][2]string{ + "media-parent": {"parent", "cid-parent"}, + "media-quoted": {"quoted", "cid-parent"}, + "media-forwarded": {"forwarded", "cid-forwarded"}, + } { + arguments := argumentsByResource[resourceID] + if arguments["message-id"] != want[0] || + arguments["open-conversation-id"] != want[1] { + t.Errorf("%s arguments = %#v, want message=%q conversation=%q", + resourceID, arguments, want[0], want[1]) + } + } +} + +func TestCrossPlatformCoverageResourceBoundaryHelpers(t *testing.T) { + encoded := map[string]any{ + "openMessageId": "parent", + "openConversationId": "cid", + "content": `{"quotedMessage":{"openMessageId":"encoded-child","mediaId":"nested"}}`, + } + if got := Resources(encoded); got != nil { + t.Fatalf("encoded nested resource bound to parent: %#v", got) + } + if got := ResourcesDeep(encoded); len(got) != 1 { + t.Fatalf("encoded nested resources = %#v", got) + } else { + arguments := got[0]["download"].(map[string]any)["arguments"].(map[string]any) + if arguments["message-id"] != "encoded-child" || + arguments["open-conversation-id"] != "cid" { + t.Fatalf("encoded child arguments = %#v", arguments) + } + } + if got := resourcesDeep(map[string]any{"mediaId": "x"}, "", maxResourceMessageDepth+1); got != nil { + t.Fatalf("over-depth resources = %#v", got) + } + if got := resourcesDeep( + map[string]any{"mediaId": "x"}, + "", + maxResourceMessageDepth, + ); len(got) != 1 { + t.Fatalf("max-depth owned resources = %#v", got) + } + if got := nestedMessageMaps([]map[string]any{{"id": "a"}}); len(got) != 1 { + t.Fatalf("map slice = %#v", got) + } + if got := nestedMessageMaps(`[{"id":"a"}]`); len(got) != 1 { + t.Fatalf("encoded message list = %#v", got) + } + if got := nestedMessageMaps([]any{"bad", map[string]any{"id": "a"}}); len(got) != 1 { + t.Fatalf("mixed message list = %#v", got) + } + if got := nestedMessageMaps("{"); got != nil { + t.Fatalf("invalid encoded messages = %#v", got) + } + if got := nestedMessageChildren([]map[string]any{ + {"content": "plain"}, + {"content": `{"forwardedMessages":[{"openMessageId":"m"}]}`}, + }); len(got) != 1 { + t.Fatalf("nested message children = %#v", got) + } + if got := nestedMessageChildren([]any{ + "plain", + map[string]any{"quoted": map[string]any{"openMessageId": "m"}}, + }); len(got) != 1 { + t.Fatalf("mixed nested message children = %#v", got) + } + if got := nestedMessageChildren("{"); len(got) != 0 { + t.Fatalf("invalid nested children = %#v", got) + } + if isNestedMessageBoundaryKey("content") { + t.Fatal("ordinary content treated as a message boundary") + } +} + func TestUpdateTimeOmitsUneditedEcho(t *testing.T) { if got := UpdateTime(map[string]any{ "createTime": "2026-07-19 13:37:03", diff --git a/internal/shortcut/semantic_catalog.json b/internal/shortcut/semantic_catalog.json index 5a45ba8d..982950b6 100644 --- a/internal/shortcut/semantic_catalog.json +++ b/internal/shortcut/semantic_catalog.json @@ -487,8 +487,8 @@ }, "+messages-mget": { "disposition": "semantic_adapter", - "semantic_delta": "按最多 50 个消息 ID 批量读取并输出稳定消息投影、reaction 与 resourceRefs;可用 --download-resources 统一下载 mediaId 与 fileId,复用 HTTPS、相对路径、无覆盖和原子落盘防护,对重复资源去重并逐资源返回下载失败 ledger。", - "risk": "read", + "semantic_delta": "按最多 50 个消息 ID 批量读取并输出稳定消息投影、reaction 与 resourceRefs;可用 --download-resources 统一下载 mediaId 与 fileId,复用受信任下载域、相对路径、无覆盖和原子落盘防护,对重复资源去重并逐资源返回下载失败 ledger;因可写本地文件按 write 风险确认。", + "risk": "write", "public": true, "reviewed": true }, @@ -564,8 +564,8 @@ }, "+messages-resource-download": { "disposition": "primary_smart", - "semantic_delta": "统一承接消息 mediaId 与钉盘 fileId:分别复用 IM 临时资源 URL 和 drive.download_file,再通过工作目录内安全路径、默认不覆盖、临时文件下载和原子发布输出结构化结果。", - "risk": "read", + "semantic_delta": "统一承接消息 mediaId 与钉盘 fileId:分别复用 IM 临时资源 URL 和 drive.download_file,只允许钉钉/OSS HTTPS 下载域且重定向复验并隔离跨域凭据,再通过工作目录内安全路径、默认不覆盖、临时文件下载和原子发布输出结构化结果。", + "risk": "write", "public": true, "reviewed": true }, @@ -628,7 +628,7 @@ "+at-me": { "disposition": "primary_smart", "semantic_delta": "自动构造时间窗,分页拉取跨会话 @我 消息,并保留身份、引用、reaction、resourceRefs 与完整性;可选对资源去重后安全落盘并返回逐项失败 ledger。", - "risk": "read", + "risk": "write", "public": true, "reviewed": true }, @@ -642,7 +642,7 @@ "+chat-messages": { "disposition": "primary_smart", "semantic_delta": "统一群聊与两类单聊目标;省略时间时自动以当前时间向前读取最近消息,并输出稳定消息身份、引用、reaction、resourceRefs、时间边界翻页和可读正文;可选对列表内资源去重后安全落盘并返回逐项失败 ledger。", - "risk": "read", + "risk": "write", "public": true, "reviewed": true }, @@ -678,7 +678,7 @@ "+search-msg": { "disposition": "primary_smart", "semantic_delta": "统一关键词、发送者、@对象、会话、消息类型和机器人来源过滤;展开下层按会话分组的 conversationMessagesList,支持精确时间窗、page-all、50 条一组 mget 富化,可选安全下载命中消息资源,并以 failure ledger 显式报告截断、富化或下载失败。", - "risk": "read", + "risk": "write", "public": true, "reviewed": true }, @@ -692,7 +692,7 @@ "+thread-replies": { "disposition": "primary_smart", "semantic_delta": "接受消息列表直接返回的 threadId(兼容 topicId),拉取回复并输出稳定身份、引用、reaction、resourceRefs、可读正文和时间边界分页;可选对回复资源去重后安全落盘并返回逐项失败 ledger。", - "risk": "read", + "risk": "write", "public": true, "reviewed": true }, diff --git a/internal/shortcut/smart/at_me.go b/internal/shortcut/smart/at_me.go index bb052a05..4c02cdc2 100644 --- a/internal/shortcut/smart/at_me.go +++ b/internal/shortcut/smart/at_me.go @@ -38,9 +38,9 @@ import ( // so it honours --format/--jq/--fields. When the response carries no // recognisable message list we fall back to printing the raw payload. // -// This replaces manually working out the millisecond time window and copying the -// list-mentions incantation. Read-only: it only searches and reshapes, never -// sends, recalls or marks anything. +// This replaces manually working out the millisecond time window and copying +// the list-mentions incantation. The default path only searches and reshapes; +// --download-resources additionally writes resource files locally. // // dws chat +at-me // dws chat +at-me --days 3 @@ -52,8 +52,8 @@ var AtMe = shortcut.Shortcut{ Intent: "当你想快速看回最近谁在群里或单聊里 @了你、但不想手动把起止时间换算成毫秒、也不想记 list-mentions 的一堆参数时使用;" + "内部按本地时区算出「最近 N 天」(默认 7 天,可用 --days 调整回溯天数)的时间窗,搜索这段时间内 @我 的消息," + "再在本地把每条消息投影成发送人、时间、内容、所在会话四个关键字段。" + - "这是纯只读操作,只做搜索与本地投影,不会发送、撤回或标记任何消息。", - Risk: shortcut.RiskRead, + "默认只读且不会发送、撤回或标记任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。", + Risk: shortcut.RiskWrite, Flags: append([]shortcut.Flag{ {Name: "days", Type: shortcut.FlagInt, Desc: "回溯天数(可选,默认 7)", Default: "7", Required: false}, {Name: "limit", Type: shortcut.FlagInt, Desc: "每页返回数量(默认 50)", Default: "50"}, diff --git a/internal/shortcut/smart/chat_messages.go b/internal/shortcut/smart/chat_messages.go index d586f5e0..54e9b1e8 100644 --- a/internal/shortcut/smart/chat_messages.go +++ b/internal/shortcut/smart/chat_messages.go @@ -37,8 +37,8 @@ import ( // aliases and one level of nesting; // 3. print via rt.Output as {messages, count} so it honours --format/--jq/--fields. // -// Read-only: it only reads a conversation's messages and reshapes them locally, -// never posts or mutates anything. +// The default path only reads and reshapes conversation messages; +// --download-resources additionally writes resource files locally. // // 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 @@ -51,8 +51,8 @@ var ChatMessages = shortcut.Shortcut{ "群聊传 --group(群会话 ID,openConversationId),单聊传 --user(对方 userId),两者互斥且必须二选一。" + "省略 --time 时默认从当前时间向前读取最近消息;也可指定时间边界并用 --direction newer/older 控制方向。" + "内部据此调用群聊或单聊的消息列表接口,再在本地投影出每条消息的发言人、文本和时间。" + - "这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。", - Risk: shortcut.RiskRead, + "默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。", + Risk: shortcut.RiskWrite, Flags: append([]shortcut.Flag{ {Name: "group", Type: shortcut.FlagString, Desc: "群会话 ID(openConversationId),与 --user 互斥"}, {Name: "conversation-id", Type: shortcut.FlagString, Desc: "--group 的别名", Hidden: true}, diff --git a/internal/shortcut/smart/coverage_completion_test.go b/internal/shortcut/smart/coverage_completion_test.go index 426f474f..0208f294 100644 --- a/internal/shortcut/smart/coverage_completion_test.go +++ b/internal/shortcut/smart/coverage_completion_test.go @@ -89,7 +89,7 @@ func TestCrossPlatformCoverageChatMessagesOpenIDRoute(t *testing.T) { }} helpers.InitDeps(caller) root := newPlatformCoverageRoot() - root.SetArgs([]string{"chat", "+chat-messages", "--open-dingtalk-id", "D-user", "--limit", "1"}) + root.SetArgs([]string{"chat", "+chat-messages", "--open-dingtalk-id", "D-user", "--limit", "1", "--yes"}) if err := root.Execute(); err != nil { t.Fatal(err) } @@ -202,7 +202,7 @@ func TestCrossPlatformCoverageSearchValidationAndTimeErrors(t *testing.T) { for _, tail := range cases { helpers.InitDeps(&smartCoverageCaller{}) root := newPlatformCoverageRoot() - root.SetArgs(append([]string{"chat", "+search-msg"}, tail...)) + root.SetArgs(append([]string{"chat", "+search-msg", "--yes"}, tail...)) if err := root.Execute(); err == nil { t.Errorf("invalid search args succeeded: %v", tail) } @@ -247,7 +247,7 @@ func TestCrossPlatformCoverageSearchPaginationFailureModes(t *testing.T) { root := newPlatformCoverageRoot() var output bytes.Buffer root.SetOut(&output) - root.SetArgs(append([]string{"chat", "+search-msg"}, tc.args...)) + root.SetArgs(append([]string{"chat", "+search-msg", "--yes"}, tc.args...)) err := root.Execute() if (err != nil) != tc.wantError { t.Fatalf("error = %v, wantError=%v", err, tc.wantError) diff --git a/internal/shortcut/smart/gap_fill_test.go b/internal/shortcut/smart/gap_fill_test.go index dc65a0c5..48bcd045 100644 --- a/internal/shortcut/smart/gap_fill_test.go +++ b/internal/shortcut/smart/gap_fill_test.go @@ -92,7 +92,7 @@ func TestCrossPlatformCoverageMessageReadShortcutResourceOutputValidation(t *tes } { helpers.InitDeps(&smartCoverageCaller{}) root := newPlatformCoverageRoot() - root.SetArgs(append(args, "--download-resources", "--output-dir", "../outside")) + root.SetArgs(append(args, "--download-resources", "--output-dir", "../outside", "--yes")) if err := root.Execute(); err == nil { t.Fatalf("unsafe output accepted: %v", args) } @@ -104,7 +104,7 @@ func TestCrossPlatformCoverageChatMessagesDefaultsToRecentHistory(t *testing.T) helpers.InitDeps(caller) root := newPlatformCoverageRoot() before := time.Now().Add(-2 * time.Second) - root.SetArgs([]string{"chat", "+chat-messages", "--group", "cid", "--limit", "5"}) + root.SetArgs([]string{"chat", "+chat-messages", "--group", "cid", "--limit", "5", "--yes"}) if err := root.Execute(); err != nil { t.Fatal(err) } @@ -133,6 +133,7 @@ func TestCrossPlatformCoverageChatMessagesPreservesExplicitTime(t *testing.T) { "chat", "+chat-messages", "--group", "cid", "--time", "2026-07-01 12:34:56", + "--yes", }) if err := root.Execute(); err != nil { t.Fatal(err) diff --git a/internal/shortcut/smart/search_msg.go b/internal/shortcut/smart/search_msg.go index f63d6613..f98c3af5 100644 --- a/internal/shortcut/smart/search_msg.go +++ b/internal/shortcut/smart/search_msg.go @@ -36,8 +36,9 @@ var SearchMsg = shortcut.Shortcut{ Product: "im", Description: "多维搜索消息,可全量翻页并批量富化详情", Intent: "当你要按关键词、发送者、@对象、会话、消息类型或机器人来源组合搜索 IM 消息时使用;默认查询近 7 天," + - "也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。", - Risk: shortcut.RiskRead, + "也可指定精确起止时间。--page-all 会连续拉取游标页,默认再按消息 ID 分批富化详情;任何续页或富化失败都会保留已取得结果并返回逐项失败 ledger,绝不把截断结果标成完整。" + + "默认只读;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。", + Risk: shortcut.RiskWrite, Flags: append([]shortcut.Flag{ {Name: "query", Type: shortcut.FlagString, Desc: "搜索关键词"}, {Name: "keyword", Type: shortcut.FlagString, Desc: "--query 的别名", Hidden: true}, diff --git a/internal/shortcut/smart/search_msg_execution_test.go b/internal/shortcut/smart/search_msg_execution_test.go index 6cabbff6..e7a66b6f 100644 --- a/internal/shortcut/smart/search_msg_execution_test.go +++ b/internal/shortcut/smart/search_msg_execution_test.go @@ -73,7 +73,7 @@ func executeSearchMsg(t *testing.T, caller *searchMsgExecutionCaller, args ...st root := newPlatformCoverageRoot() var output bytes.Buffer root.SetOut(&output) - root.SetArgs(append([]string{"chat", "+search-msg"}, args...)) + root.SetArgs(append([]string{"chat", "+search-msg", "--yes"}, args...)) if err := root.Execute(); err != nil { t.Fatal(err) } diff --git a/internal/shortcut/smart/thread_replies.go b/internal/shortcut/smart/thread_replies.go index c9c7ccc7..7307b65d 100644 --- a/internal/shortcut/smart/thread_replies.go +++ b/internal/shortcut/smart/thread_replies.go @@ -30,8 +30,8 @@ import ( // project each reply to {sender, text, createTime} tolerating field aliases; // 3. print via rt.Output as {replies, count} so it honours --format/--jq/--fields. // -// Read-only: it only reads a topic's replies and reshapes them locally, never -// posts or mutates anything. +// The default path only reads and reshapes topic replies; +// --download-resources additionally writes resource files locally. // // dws chat +thread-replies --group --thread-id var ThreadReplies = shortcut.Shortcut{ @@ -42,8 +42,8 @@ var ThreadReplies = shortcut.Shortcut{ Intent: "当你已经拿到某个群里一条「话题消息」的 threadId/topicId、想快速看这条话题下的全部回复(谁在什么时间回复了什么)," + "而不想拿到一大坨原始消息字段时使用;内部按 --group(群会话 ID)和 --thread-id(兼容 --topic-id)拉取该话题的回复列表," + "可选 --time 指定起始时间、--limit 指定每页条数,再在本地投影出每条回复的发言人、文本和回复时间。" + - "这是纯只读操作,只做拉取与本地投影,不会发送或修改任何消息。", - Risk: shortcut.RiskRead, + "默认只读且不会发送或修改任何消息;传 --download-resources 时会在工作目录写文件,因此命令按本地写入操作确认。", + Risk: shortcut.RiskWrite, Flags: append([]shortcut.Flag{ {Name: "group", Type: shortcut.FlagString, Desc: "群会话 ID(openConversationId,必填)", Required: true}, {Name: "thread-id", Type: shortcut.FlagString, Desc: "话题/线程 ID(可直接使用消息列表返回的 threadId)"},