From 95bbaa119f6aa02496d6f1203f2a821f833fc613 Mon Sep 17 00:00:00 2001 From: GatewayJ <835269233@qq.com> Date: Tue, 26 May 2026 19:01:15 +0800 Subject: [PATCH 1/2] fix(feishu): allow open_id mentions --- cli/csgclawcli/app_test.go | 2 +- cli/message/message.go | 2 +- docs/cli.md | 7 ++-- docs/cli.zh.md | 7 ++-- internal/channel/feishu/service.go | 29 ++++++++++------ internal/channel/feishu/service_test.go | 34 ++++++++++++++++++- .../workspace/skills/basics/SKILL.md | 17 ++++++++-- .../workspace/skills/basics/SKILL.md | 17 ++++++++-- 8 files changed, 92 insertions(+), 23 deletions(-) diff --git a/cli/csgclawcli/app_test.go b/cli/csgclawcli/app_test.go index e015b072..5e6a9375 100644 --- a/cli/csgclawcli/app_test.go +++ b/cli/csgclawcli/app_test.go @@ -128,7 +128,7 @@ func TestExecuteBotIdentityHelpUsesBotIDSemantics(t *testing.T) { { name: "message create", args: []string{"message", "create", "--help"}, - want: []string{"sender bot id", "mentioned bot id"}, + want: []string{"sender bot id", "mentioned target id from member list"}, }, } diff --git a/cli/message/message.go b/cli/message/message.go index 981bd673..1092a126 100644 --- a/cli/message/message.go +++ b/cli/message/message.go @@ -78,7 +78,7 @@ func (c cmd) runCreate(ctx context.Context, run *command.Context, args []string, roomID := fs.String("room-id", "", "target room id") senderID := fs.String("sender-id", "", "sender bot id") content := fs.String("content", "", "message content") - mentionID := fs.String("mention-id", "", "mentioned bot id") + mentionID := fs.String("mention-id", "", "mentioned target id from member list") if err := fs.Parse(args); err != nil { return err } diff --git a/docs/cli.md b/docs/cli.md index ca7fe704..c9d8af52 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -562,7 +562,9 @@ Subcommands: - `--room-id string`: required. - `--sender-id string`: required sender bot ID. - `--content string`: required. -- `--mention-id string`: optional mentioned bot ID. +- `--mention-id string`: optional mentioned target ID from `member list`. + For Feishu rooms this can be a CSGClaw bot ID such as `u-dev` or a human + Feishu open_id such as `ou_xxx`. `message list` behavior: @@ -640,6 +642,7 @@ csgclaw-cli room create --channel feishu --title "ops-room" --creator-id u-manag csgclaw-cli member list --channel feishu --room-id oc_x csgclaw-cli member create --channel feishu --room-id oc_x --user-id u-dev --inviter-id u-manager csgclaw-cli message create --channel feishu --room-id oc_x --sender-id u-manager --mention-id u-dev --content hello +csgclaw-cli message create --channel feishu --room-id oc_x --sender-id u-manager --mention-id ou_xxx --content hello ``` -`csgclaw-cli` is the bot-facing CLI. It should not require callers to know or pass agent IDs, Feishu open IDs, Feishu app IDs, App ID/App Secret, or other channel credentials in room, member, or message commands. Channel-specific adapters are responsible for exchanging bot IDs for the identifiers required by the target channel. +`csgclaw-cli` is the bot-facing CLI. It should not require callers to know or pass agent IDs, Feishu app IDs, App ID/App Secret, or other channel credentials in room, member, or message commands. Feishu human mentions are the exception: use `member list` and pass the returned `id` field, which is an `ou_xxx` open_id for human members and a `u-*` bot ID for CSGClaw bots. diff --git a/docs/cli.zh.md b/docs/cli.zh.md index 00c239ce..013ecf28 100644 --- a/docs/cli.zh.md +++ b/docs/cli.zh.md @@ -562,7 +562,9 @@ csgclaw message [flags] - `--room-id string`:必填。 - `--sender-id string`:必填,发送方 bot ID。 - `--content string`:必填。 -- `--mention-id string`:可选,被提及 bot ID。 +- `--mention-id string`:可选,被提及目标 ID,来自 `member list` 输出。 + 对飞书房间,可以是 `u-dev` 这类 CSGClaw bot ID,也可以是 `ou_xxx` + 这类真人飞书 open_id。 `message list` 行为说明: @@ -640,6 +642,7 @@ csgclaw-cli room create --channel feishu --title "ops-room" --creator-id u-manag csgclaw-cli member list --channel feishu --room-id oc_x csgclaw-cli member create --channel feishu --room-id oc_x --user-id u-dev --inviter-id u-manager csgclaw-cli message create --channel feishu --room-id oc_x --sender-id u-manager --mention-id u-dev --content hello +csgclaw-cli message create --channel feishu --room-id oc_x --sender-id u-manager --mention-id ou_xxx --content hello ``` -`csgclaw-cli` 是面向 bot 的 CLI。room、member、message 命令不应要求调用方理解或传入 agent ID、飞书 open_id、飞书 app_id、App ID/App Secret 或其他渠道凭证。各 channel adapter 负责把 bot ID 转换成目标渠道需要的标识。 +`csgclaw-cli` 是面向 bot 的 CLI。room、member、message 命令不应要求调用方理解或传入 agent ID、飞书 app_id、App ID/App Secret 或其他渠道凭证。飞书真人 @ 是例外:先用 `member list`,再传返回结果中的 `id` 字段;真人成员是 `ou_xxx` open_id,CSGClaw bot 成员是 `u-*` bot ID。 diff --git a/internal/channel/feishu/service.go b/internal/channel/feishu/service.go index ab4e80a4..d0fea8e5 100644 --- a/internal/channel/feishu/service.go +++ b/internal/channel/feishu/service.go @@ -927,15 +927,19 @@ func defaultSendMessage(ctx context.Context, app AppConfig, req SendMessageReque mentionID := strings.TrimSpace(req.MentionID) mentionOpenID := "" if mentionID != "" { - mentionApp, err := validateAppConfig(req.MentionAppConfig, mentionID) - if err != nil { - return SendMessageResponse{}, err - } - botInfo, err := fetchBotInfo(ctx, mentionApp) - if err != nil { - return SendMessageResponse{}, err + if isFeishuOpenID(mentionID) { + mentionOpenID = mentionID + } else { + mentionApp, err := validateAppConfig(req.MentionAppConfig, mentionID) + if err != nil { + return SendMessageResponse{}, err + } + botInfo, err := fetchBotInfo(ctx, mentionApp) + if err != nil { + return SendMessageResponse{}, err + } + mentionOpenID = botInfo.OpenID } - mentionOpenID = botInfo.OpenID text = fmt.Sprintf("%s %s", mentionOpenID, mentionID, req.Content) } @@ -1025,7 +1029,7 @@ func (s *Service) SendMessage(req im.CreateMessageRequest) (im.Message, error) { app, err := s.appConfigForSenderLocked(senderID) mentionID := strings.TrimSpace(req.MentionID) var mentionApp AppConfig - if err == nil && mentionID != "" { + if err == nil && mentionID != "" && !isFeishuOpenID(mentionID) { mentionApp, err = s.appConfigForMentionLocked(mentionID) } s.mu.RUnlock() @@ -1508,7 +1512,12 @@ func (s *Service) appConfigForMentionLocked(mention string) (AppConfig, error) { if app, ok := s.apps[mention]; ok { return validateAppConfig(app, mention) } - return AppConfig{}, fmt.Errorf("feishu app is not configured for mention %q", mention) + return AppConfig{}, fmt.Errorf("mention_id %q is neither a configured CSGClaw bot id nor a Feishu open_id; use csgclaw-cli member list --room-id --channel feishu and pass the id field", mention) +} + +func isFeishuOpenID(id string) bool { + id = strings.TrimSpace(id) + return strings.HasPrefix(id, "ou_") && len(id) > len("ou_") } func (s *Service) managerAppConfigLocked() (AppConfig, error) { diff --git a/internal/channel/feishu/service_test.go b/internal/channel/feishu/service_test.go index 58c0581e..0a6af73d 100644 --- a/internal/channel/feishu/service_test.go +++ b/internal/channel/feishu/service_test.go @@ -457,6 +457,38 @@ func TestFeishuSendMessageResolvesMentionApp(t *testing.T) { } } +func TestFeishuSendMessageAcceptsOpenIDMention(t *testing.T) { + var gotReq SendMessageRequest + svc := NewServiceWithSendMessage( + map[string]AppConfig{"u-manager": {AppID: "cli_manager", AppSecret: "manager-secret"}}, + func(_ context.Context, _ AppConfig, req SendMessageRequest) (SendMessageResponse, error) { + gotReq = req + return SendMessageResponse{MessageID: "om_mention", SenderOpenID: "ou_manager", MentionOpenID: "ou_human"}, nil + }, + ) + svc.rooms["oc_alpha"] = &im.Room{ID: "oc_alpha", Title: "alpha", Members: []string{"u-manager", "ou_human"}} + + message, err := svc.SendMessage(im.CreateMessageRequest{ + RoomID: "oc_alpha", + SenderID: "u-manager", + Content: "hello", + MentionID: "ou_human", + }) + if err != nil { + t.Fatalf("SendMessage() error = %v", err) + } + + if gotReq.MentionID != "ou_human" { + t.Fatalf("send request mention id = %q, want ou_human", gotReq.MentionID) + } + if gotReq.MentionAppConfig.AppID != "" || gotReq.MentionAppConfig.AppSecret != "" { + t.Fatalf("send request mention app config = %+v, want empty for open_id mention", gotReq.MentionAppConfig) + } + if len(message.Mentions) != 1 || message.Mentions[0].ID != "ou_human" || message.Mentions[0].Name != "ou_human" { + t.Fatalf("message mentions = %+v, want ou_human", message.Mentions) + } +} + func TestFeishuSendMessageWithMentionPublishesMessageEvent(t *testing.T) { svc := NewServiceWithSendMessage( map[string]AppConfig{ @@ -544,7 +576,7 @@ func TestFeishuSendMessageRequiresMentionApp(t *testing.T) { Content: "hello", MentionID: "u-dev", }) - if err == nil || !strings.Contains(err.Error(), `feishu app is not configured for mention "u-dev"`) { + if err == nil || !strings.Contains(err.Error(), `mention_id "u-dev" is neither a configured CSGClaw bot id nor a Feishu open_id`) { t.Fatalf("SendMessage() error = %v, want mention app config error", err) } } diff --git a/internal/templates/embed/openclaw-manager/workspace/skills/basics/SKILL.md b/internal/templates/embed/openclaw-manager/workspace/skills/basics/SKILL.md index 7fcd2bed..f281b777 100644 --- a/internal/templates/embed/openclaw-manager/workspace/skills/basics/SKILL.md +++ b/internal/templates/embed/openclaw-manager/workspace/skills/basics/SKILL.md @@ -95,12 +95,23 @@ csgclaw-cli room create \ --channel feishu ``` -Send a message with a mention. Use the mentioned bot ID for `--mention-id`: +Send a message with a mention. Use the target member ID for `--mention-id`: ```bash -csgclaw-cli message create --room-id oc_xxx --sender-id u-manager --content "Please take a look." --mention-id u-alex --channel +csgclaw-cli message create --room-id oc_xxx --sender-id u-manager --content "Please take a look." --mention-id u-dev --channel ``` +For Feishu rooms, use `member list` as the source of truth for mentions: + +```bash +csgclaw-cli member list --room-id oc_xxx --channel feishu +``` + +Pick the target member by `name`, then pass that row's `id` as `--mention-id`. +CSGClaw bots use IDs such as `u-dev` or `u-qa`; human Feishu users use +open_id values such as `ou_xxx`. Do not pass display names, Chinese names, or +handles as `--mention-id`. + ## Operating Rules - Prefer direct `csgclaw-cli` commands over ad hoc HTTP calls. @@ -108,6 +119,6 @@ csgclaw-cli message create --room-id oc_xxx --sender-id u-manager --content "Ple - When creating a bot, always pass a meaningful `--description` so later matching and reuse remain clear. - Verify room membership with `member list` after adding a member when room presence matters. - A direct room cannot accept an added bot as a new member. Create a new room with `--member-ids` containing the existing DM bots and the new bot. -- Keep `csgclaw-cli` parameters bot-facing across channels: use bot IDs such as `u-manager`, `u-dev`, and `u-alex`. +- Keep `csgclaw-cli` parameters bot-facing unless a command explicitly asks for member-list IDs. For Feishu human `--mention-id`, pass the returned `ou_xxx` id. - Keep the response focused on the concrete CLI result instead of introducing external planning artifacts. - Hand off to `manager-worker-dispatch` only if the user explicitly needs manager orchestration or multi-worker sequencing. diff --git a/internal/templates/embed/picoclaw-manager/workspace/skills/basics/SKILL.md b/internal/templates/embed/picoclaw-manager/workspace/skills/basics/SKILL.md index 7fcd2bed..f281b777 100644 --- a/internal/templates/embed/picoclaw-manager/workspace/skills/basics/SKILL.md +++ b/internal/templates/embed/picoclaw-manager/workspace/skills/basics/SKILL.md @@ -95,12 +95,23 @@ csgclaw-cli room create \ --channel feishu ``` -Send a message with a mention. Use the mentioned bot ID for `--mention-id`: +Send a message with a mention. Use the target member ID for `--mention-id`: ```bash -csgclaw-cli message create --room-id oc_xxx --sender-id u-manager --content "Please take a look." --mention-id u-alex --channel +csgclaw-cli message create --room-id oc_xxx --sender-id u-manager --content "Please take a look." --mention-id u-dev --channel ``` +For Feishu rooms, use `member list` as the source of truth for mentions: + +```bash +csgclaw-cli member list --room-id oc_xxx --channel feishu +``` + +Pick the target member by `name`, then pass that row's `id` as `--mention-id`. +CSGClaw bots use IDs such as `u-dev` or `u-qa`; human Feishu users use +open_id values such as `ou_xxx`. Do not pass display names, Chinese names, or +handles as `--mention-id`. + ## Operating Rules - Prefer direct `csgclaw-cli` commands over ad hoc HTTP calls. @@ -108,6 +119,6 @@ csgclaw-cli message create --room-id oc_xxx --sender-id u-manager --content "Ple - When creating a bot, always pass a meaningful `--description` so later matching and reuse remain clear. - Verify room membership with `member list` after adding a member when room presence matters. - A direct room cannot accept an added bot as a new member. Create a new room with `--member-ids` containing the existing DM bots and the new bot. -- Keep `csgclaw-cli` parameters bot-facing across channels: use bot IDs such as `u-manager`, `u-dev`, and `u-alex`. +- Keep `csgclaw-cli` parameters bot-facing unless a command explicitly asks for member-list IDs. For Feishu human `--mention-id`, pass the returned `ou_xxx` id. - Keep the response focused on the concrete CLI result instead of introducing external planning artifacts. - Hand off to `manager-worker-dispatch` only if the user explicitly needs manager orchestration or multi-worker sequencing. From 1ba767929d456badb180c809eefcc11556a3d477 Mon Sep 17 00:00:00 2001 From: GatewayJ <835269233@qq.com> Date: Tue, 26 May 2026 19:13:40 +0800 Subject: [PATCH 2/2] docs: clarify Feishu mention ids --- docs/api.md | 3 +++ docs/api.zh.md | 3 +++ 2 files changed, 6 insertions(+) diff --git a/docs/api.md b/docs/api.md index bd10c434..19628dc0 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1010,6 +1010,9 @@ Example send-message request: } ``` +For Feishu mentions, `mention_id` can be a configured CSGClaw bot ID such as +`u-worker`, or a human Feishu open_id such as `ou_xxx` returned by member list. + ## Bot Compatibility API These endpoints live under `/api/bots/{id}` and exist for compatibility with the older PicoClaw bot integration. diff --git a/docs/api.zh.md b/docs/api.zh.md index 3bb5e5c5..9b8eb3dc 100644 --- a/docs/api.zh.md +++ b/docs/api.zh.md @@ -1004,6 +1004,9 @@ context 不会被渲染成 thread 内消息;它是给 LLM-backed agent 使用 } ``` +飞书消息的 `mention_id` 可以是 `u-worker` 这类已配置的 CSGClaw bot ID, +也可以是 member list 返回的 `ou_xxx` 这类真人飞书 open_id。 + ## Bot 兼容 API 这组接口位于 `/api/bots/{id}`,用于兼容旧的 PicoClaw Bot 接入方式。