Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/csgclawcli/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
},
}

Expand Down
2 changes: 1 addition & 1 deletion cli/message/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
3 changes: 3 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions docs/api.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 接入方式。
Expand Down
7 changes: 5 additions & 2 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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.
7 changes: 5 additions & 2 deletions docs/cli.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,9 @@ csgclaw message <subcommand> [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` 行为说明:

Expand Down Expand Up @@ -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
29 changes: 19 additions & 10 deletions internal/channel/feishu/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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("<at user_id=\"%s\">%s</at> %s", mentionOpenID, mentionID, req.Content)
}

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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 <room> --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) {
Expand Down
34 changes: 33 additions & 1 deletion internal/channel/feishu/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,19 +95,30 @@ 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 <current_channel>
csgclaw-cli message create --room-id oc_xxx --sender-id u-manager --content "Please take a look." --mention-id u-dev --channel <current_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.
- Use `bot list` before creating a new bot if the user may be referring to an existing one.
- 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.
Original file line number Diff line number Diff line change
Expand Up @@ -95,19 +95,30 @@ 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 <current_channel>
csgclaw-cli message create --room-id oc_xxx --sender-id u-manager --content "Please take a look." --mention-id u-dev --channel <current_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.
- Use `bot list` before creating a new bot if the user may be referring to an existing one.
- 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.