From 2b2bbbb6b99b1218975a58b5039eec8a55878a80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BF=AE=E9=9B=A8?= Date: Mon, 29 Jun 2026 17:59:19 +0800 Subject: [PATCH] feat(chat): default --ai-tag on so dws-sent messages carry the AI badge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per req 83667761 (奕皓): messages sent through dws should carry the 「通过AI发送」badge by default, transparently flagging AI/CLI-sent messages. - `--ai-tag` default flipped false → true on `chat message send` / `reply`, so no flag / `--ai-tag` / `--ai-tag=true` all attach clawType (open edition `openClaw`); only `--ai-tag=false` omits it (send as the user). The switch name is unchanged. reply honors the same default (no longer leaks the wukong clawType). - skill chat.md: concise rule — default-on, pass `--ai-tag=false` to disable. - tests: default now asserts clawType present; added an `--ai-tag=false` opt-out case. --- internal/helpers/chat.go | 11 +++++----- internal/helpers/chat_test.go | 27 +++++++++++++++++++++---- skills/mono/references/products/chat.md | 9 ++++++++- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/internal/helpers/chat.go b/internal/helpers/chat.go index 7fa4faa5..7accbc87 100644 --- a/internal/helpers/chat.go +++ b/internal/helpers/chat.go @@ -334,13 +334,14 @@ func newChatMessageSendCommand(runner executor.Runner) *cobra.Command { cmd.Flags().String("file-type", "", "文件类型/扩展名 (msg-type=file)") cmd.Flags().String("file-path", "", "文件展示路径 (msg-type=file)") cmd.Flags().Int64("file-size", 0, "文件大小,单位字节 (msg-type=file)") - cmd.Flags().Bool("ai-tag", false, "标记为「通过AI发送」(默认不带;仅传 --ai-tag 时才在消息下方显示 AI 发送角标)") + cmd.Flags().Bool("ai-tag", true, "标记为「通过AI发送」角标,默认带上(透明标识 AI/CLI 代发);仅当 --ai-tag=false 时不带角标(按本人发送)") return cmd } -// attachAITag 仅在用户显式传入 --ai-tag 时,给发送参数加上 clawType, -// 由 IM 服务端据此渲染「通过AI发送」角标 (悟空版渲染「悟空AI发送」)。 -// 默认不带:是否标记 AI 发送交由用户自行选择,不强加。 +// attachAITag 在 --ai-tag 为真时给发送参数加上 clawType,由 IM 服务端据此 +// 渲染「通过AI发送」角标 (悟空版渲染「悟空AI发送」)。--ai-tag 默认 true: +// 经 dws/agent 代发的消息默认带角标以透明标识 AI/CLI 代发,仅当用户显式 +// 传 --ai-tag=false 时才不带 (按本人发送)。 func attachAITag(cmd *cobra.Command, params map[string]any) { if on, _ := cmd.Flags().GetBool("ai-tag"); on { params["clawType"] = edition.ClawType() @@ -1017,7 +1018,7 @@ func newChatMessageReplyCommand(runner executor.Runner) *cobra.Command { cmd.Flags().String("ref-sender", "", "被引用消息发送者 openDingTalkId (必填)") cmd.Flags().String("text", "", "回复正文 (必填)") cmd.Flags().String("uuid", "", "可选 uuid(幂等标识)") - cmd.Flags().Bool("ai-tag", false, "标记为「通过AI发送」(默认不带;仅传 --ai-tag 时才显示 AI 发送角标)") + cmd.Flags().Bool("ai-tag", true, "标记为「通过AI发送」角标,默认带上(透明标识 AI/CLI 代发);仅当 --ai-tag=false 时不带角标(按本人发送)") return cmd } diff --git a/internal/helpers/chat_test.go b/internal/helpers/chat_test.go index 3ed0d190..f8ae6059 100644 --- a/internal/helpers/chat_test.go +++ b/internal/helpers/chat_test.go @@ -354,8 +354,8 @@ func TestChatMessageAITagControlsClawType(t *testing.T) { }, } for _, tc := range cases { - // Default: no --ai-tag → must omit clawType entirely (no badge). - t.Run(tc.name+"/default-no-tag", func(t *testing.T) { + // Default: no --ai-tag → ai-tag defaults to true → must attach clawType. + t.Run(tc.name+"/default-has-tag", func(t *testing.T) { runner := &captureRunner{} cmd := tc.make(runner) var out bytes.Buffer @@ -365,11 +365,30 @@ func TestChatMessageAITagControlsClawType(t *testing.T) { if err := cmd.Execute(); err != nil { t.Fatalf("Execute() error = %v\noutput:\n%s", err, out.String()) } + got, ok := runner.last.Params["clawType"] + if !ok { + t.Fatalf("default send must attach clawType (ai-tag defaults true); got %#v", runner.last.Params) + } + if got != edition.DefaultOSSClawType { + t.Fatalf("clawType = %#v, want %q", got, edition.DefaultOSSClawType) + } + }) + // Opt-out: --ai-tag=false → omit clawType entirely (no badge). + t.Run(tc.name+"/ai-tag-false", func(t *testing.T) { + runner := &captureRunner{} + cmd := tc.make(runner) + var out bytes.Buffer + cmd.SetOut(&out) + cmd.SetErr(&out) + cmd.SetArgs(append(append([]string{}, tc.args...), "--ai-tag=false")) + if err := cmd.Execute(); err != nil { + t.Fatalf("Execute() error = %v\noutput:\n%s", err, out.String()) + } if v, ok := runner.last.Params["clawType"]; ok { - t.Fatalf("default send must omit clawType, got %#v", v) + t.Fatalf("--ai-tag=false must omit clawType, got %#v", v) } }) - // Opt-in: --ai-tag → attach the edition claw identity. + // Opt-in (explicit): --ai-tag → attach the edition claw identity. t.Run(tc.name+"/with-ai-tag", func(t *testing.T) { runner := &captureRunner{} cmd := tc.make(runner) diff --git a/skills/mono/references/products/chat.md b/skills/mono/references/products/chat.md index e3c491d7..ec60f8dd 100644 --- a/skills/mono/references/products/chat.md +++ b/skills/mono/references/products/chat.md @@ -388,6 +388,8 @@ Flags: **重要:该接口会真实发送消息到目标会话,不可用于测试或试探性调用。调用前必须确认消息内容和接收对象无误。** +**`--ai-tag` 默认开(默认 true)**:dws 发送的消息默认带「通过AI发送」角标,正常发无需特意加;仅当用户要求按本人发、不带角标时传 `--ai-tag=false`。仅 send / reply 支持。 + --group 指定群聊 openConversationId 发群消息;--user 指定用户 userId 发单聊;--open-dingtalk-id 指定用户 openDingTalkId 发单聊。三者只能选其一,不能同时指定。纯文本/Markdown 单聊传 --user 时直接走 userId 发送能力,不需要先手动查询 openDingTalkId。推荐使用 --text flag 传递消息内容(也支持位置参数)。可选 --title 作为消息标题。 若用户只提供了数字群号而非 openConversationId,需先调用 `chat group get-by-group-id` 将群号转为 openConversationId,再传入 --group。 --群聊时可选 --at-all @所有人,或 --at-open-dingtalk-ids 指定成员(仅群聊时生效)。 @@ -411,6 +413,10 @@ Example: dws chat message send --group --text "hello" --uuid "unique-id-123" dws chat message send --group --at-all "@all 请大家注意" dws chat message send --group --at-open-dingtalk-ids openDingTalkId1,openDingTalkId2 "<@openDingTalkId1> <@openDingTalkId2> 请查收" + # 默认即带「通过AI发送」角标,无需特意加 --ai-tag + dws chat message send --user --text "已处理好了" + # 仅当用户要求按本人发送、不带角标时 + dws chat message send --user --text "已处理好了" --ai-tag=false # 发送图片 dws chat message send --group --msg-type image --media-id # 发送文件(音频/视频/文档等非图片文件统一走钉盘上传) @@ -426,6 +432,7 @@ Flags: --title string 消息标题(可选,默认「消息」) --at-all @所有人(仅群聊时生效,可选,默认 false) --at-open-dingtalk-ids string @指定成员的 openDingTalkId 列表,逗号分隔(仅群聊时生效,可选) + --ai-tag 标记为「通过AI发送」角标,默认 true(默认带上);传 --ai-tag=false 关闭(按本人发送) --media-id string 图片 mediaId(dt_media_upload 上传后用 `python scripts/extract_media_id.py ` 提取,仅 msgType=image) --msg-type string 消息类型: image/file(image 用 mediaId,file 用钉盘上传) --dentry-id int64 钉盘文件 dentryId(msgType=file 时必填,通过 drive info 获取) @@ -1517,7 +1524,7 @@ Flags: - `chat message reply` 引用回复消息(**单聊/群聊均可**),需传 --conversation-id(openConversationId,单聊与群聊使用同一字段)、--ref-msg-id(被引用消息 openMessageId)、--ref-sender(被引用消息发送者 openDingTalkId)、--text(回复内容);目前回复类型仅支持 text - `chat message forward` 转发单条消息(**源/目标会话均支持单聊/群聊**,常见组合:群→群、群→单、单→群、单→单),需传 --src-conversation-id(源会话 openConversationId)、--msg-id(源消息 openMessageId)、--dest-conversation-id(目标会话 openConversationId) - `chat set-top` 设置/取消会话置顶(**单聊/群聊均可**),需传 --conversation-id(openConversationId,单聊与群聊使用同一字段),默认置顶,传 --off 取消 -- `chat message reply` 以当前用户身份引用回复,与 `chat message send` 的用户身份发送语义一致 +- `chat message reply` 以当前用户身份引用回复,与 `chat message send` 的用户身份发送语义一致;**同样支持 `--ai-tag`(默认 true,默认带「通过AI发送」角标,传 `--ai-tag=false` 关闭)**(详见上文 send 段的「AI 代发标记」规则) - **如何获取 openConversationId**(如果上层已有则直接使用,不必再查): - 群聊:`dws chat search --query "群名"` - 单聊:`dws chat conversation-info --user ` 或 `dws chat conversation-info --open-dingtalk-id `(人员信息可通过 `dws aisearch person --keyword "姓名" --dimension name` 获取)