Skip to content

Commit cac4a7b

Browse files
committed
Feat: Adopt ChatID of PicoClaw
1 parent 636f909 commit cac4a7b

21 files changed

Lines changed: 335 additions & 178 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ TARGET_ARCH ?= $(shell $(GO) env GOARCH)
2323
CLI_BIN ?= $(BIN_DIR)/csgclaw-cli
2424

2525
IMAGE ?= opencsg-registry.cn-beijing.cr.aliyuncs.com/opencsghq/picoclaw
26-
TAG ?= 2026.4.27.0
26+
TAG ?= 2026.5.27
2727
LOCAL_IMAGE ?= picoclaw:local
2828

2929
.DEFAULT_GOAL := build-all

docs/api.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,9 +1058,11 @@ Example request body:
10581058
}
10591059
```
10601060

1061-
`thread_root_id` is optional. When present, the bot response is sent as a reply
1062-
inside that IM thread. The detailed response behavior depends on the
1063-
compatibility bridge implementation.
1061+
`thread_root_id`, `topic_id`, and `context.topic_id` are optional thread/topic
1062+
identifiers. When one is present, the bot response is sent as a reply inside
1063+
that IM thread. When all are omitted, the response is sent as a top-level room/DM
1064+
message; the server does not infer a thread from the bot's most recent room
1065+
event.
10641066

10651067
PicoClaw outbound message shape is also accepted:
10661068

docs/api.zh.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1051,7 +1051,10 @@ prompt context 使用;它不是 thread reply 列表。PicoClaw 原生 client
10511051
}
10521052
```
10531053

1054-
`thread_root_id` 可选;传入时 bot 响应会发送到该 IM thread 中。具体响应由兼容桥实现决定。
1054+
`thread_root_id``topic_id``context.topic_id` 都是可选的 thread/topic
1055+
标识;传入任一字段时 bot 响应会发送到该 IM thread 中。全部省略时,
1056+
响应会作为 room/DM 顶层消息发送;服务端不会根据 bot 在房间中最近收到的
1057+
事件推断 thread。
10551058

10561059
也接受 PicoClaw outbound message 形态:
10571060

docs/im-threads.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,9 @@ Thread-aware bot events may include:
158158
Bot sends may include either CSGClaw fields (`room_id`, `text`,
159159
`thread_root_id`) or PicoClaw outbound fields (`chat_id`, `content`,
160160
`context.topic_id`). When a thread root/topic is present, the message is sent as
161-
a reply in that thread.
161+
a reply in that thread. Bot sends that omit `thread_root_id`, `topic_id`, and
162+
`context.topic_id` are treated as top-level room/DM messages; CSGClaw does not
163+
infer a thread from the bot's most recent room event.
162164

163165
This maps to PicoClaw/topic isolation requirements: a runtime should treat
164166
`room_id` as the normal conversation key and `room_id:thread_root_id` as the

docs/im-threads.zh.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,9 @@ Thread-aware bot event 可能包含:
148148
Bot send 可以传入 CSGClaw 字段(`room_id``text``thread_root_id`),
149149
也可以传入 PicoClaw outbound 字段(`chat_id``content`
150150
`context.topic_id`)。存在 thread root/topic 时,消息会作为该 thread 内的
151-
reply 发送。
151+
reply 发送。如果 bot send 同时省略 `thread_root_id``topic_id`
152+
`context.topic_id`,CSGClaw 会按 room/DM 顶层消息处理,不会根据该 bot 在
153+
房间中最近收到的事件推断 thread。
152154

153155
这对应 PicoClaw/topic 隔离需求:runtime 应把 `room_id` 视为普通会话 key,
154156
`room_id:thread_root_id` 视为 thread 会话 key。生成的 PicoClaw config

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ go 1.26.2
55
require github.com/larksuite/oapi-sdk-go/v3 v3.5.3
66

77
require (
8-
github.com/go-chi/chi/v5 v5.2.5
98
github.com/gin-gonic/gin v1.10.1
10-
github.com/router-for-me/CLIProxyAPI/v6 v6.9.40
9+
github.com/go-chi/chi/v5 v5.2.5
1110
github.com/pelletier/go-toml/v2 v2.2.2
11+
github.com/router-for-me/CLIProxyAPI/v6 v6.10.9
1212
golang.org/x/term v0.37.0
1313
)
1414

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
7575
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
7676
github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
7777
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
78-
github.com/router-for-me/CLIProxyAPI/v6 v6.9.40 h1:DM3Prm8+pxzMrh6UfHdbYvnEU/8ZOxDZOMQFooJw1Rw=
79-
github.com/router-for-me/CLIProxyAPI/v6 v6.9.40/go.mod h1:P1jsIPFXorYGuS2N/3BlZYkpRKi/z7+oR3+1tdG0u4k=
78+
github.com/router-for-me/CLIProxyAPI/v6 v6.10.9 h1:pGteumLPbwMXtruCTkLbsIgFa0WVlXsyDF7B8drAArs=
79+
github.com/router-for-me/CLIProxyAPI/v6 v6.10.9/go.mod h1:P1jsIPFXorYGuS2N/3BlZYkpRKi/z7+oR3+1tdG0u4k=
8080
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
8181
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
8282
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=

internal/api/bot_compat.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -360,9 +360,6 @@ func (h *Handler) handleBotSendMessage(w http.ResponseWriter, r *http.Request, b
360360
roomID := req.ResolvedRoomID()
361361
text := req.ResolvedText()
362362
threadRootID := req.ResolvedThreadRootID()
363-
if threadRootID == "" && h.botBridge != nil {
364-
threadRootID = h.botBridge.ThreadRootForReply(botID, roomID)
365-
}
366363

367364
message, err := h.im.DeliverMessage(im.DeliverMessageRequest{
368365
RoomID: roomID,

internal/api/handler_test.go

Lines changed: 2 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3334,7 +3334,7 @@ func TestHandleBotSendMessageRequiresIMService(t *testing.T) {
33343334
}
33353335
}
33363336

3337-
func TestHandleBotSendMessageDefaultsToRecentThreadScope(t *testing.T) {
3337+
func TestHandleBotSendMessageDoesNotInferRecentThreadScope(t *testing.T) {
33383338
now := time.Now().UTC()
33393339
imSvc := im.NewServiceFromBootstrap(im.Bootstrap{
33403340
CurrentUserID: "u-admin",
@@ -3418,58 +3418,8 @@ func TestHandleBotSendMessageDefaultsToRecentThreadScope(t *testing.T) {
34183418
if reply.ID == "" {
34193419
t.Fatalf("sent message %q not found in room messages", sent.MessageID)
34203420
}
3421-
if reply.RelatesTo == nil || reply.RelatesTo.RelType != im.RelationTypeThread || reply.RelatesTo.EventID != "msg-root" {
3422-
t.Fatalf("reply.RelatesTo = %+v, want m.thread -> msg-root", reply.RelatesTo)
3423-
}
3424-
3425-
topLevel, err := imSvc.CreateMessage(im.CreateMessageRequest{
3426-
RoomID: "room-1",
3427-
SenderID: "u-admin",
3428-
Content: "top-level follow-up",
3429-
})
3430-
if err != nil {
3431-
t.Fatalf("CreateMessage(top-level follow-up) error = %v", err)
3432-
}
3433-
room, ok = imSvc.Room("room-1")
3434-
if !ok {
3435-
t.Fatal("Room(room-1) = false before top-level publish, want room")
3436-
}
3437-
bridge.PublishMessageEvent(room, sender, topLevel)
3438-
select {
3439-
case evt := <-events:
3440-
if evt.ThreadRootID != "" {
3441-
t.Fatalf("top-level bot event ThreadRootID = %q, want empty", evt.ThreadRootID)
3442-
}
3443-
bridge.Ack("u-manager", evt.MessageID)
3444-
case <-time.After(time.Second):
3445-
t.Fatal("PublishMessageEvent() timed out waiting for top-level event")
3446-
}
3447-
3448-
req = httptest.NewRequest(http.MethodPost, "/api/bots/u-manager/messages/send", strings.NewReader(`{"room_id":"room-1","text":"top-level answer"}`))
3449-
rec = httptest.NewRecorder()
3450-
srv.Routes().ServeHTTP(rec, req)
3451-
if rec.Code != http.StatusOK {
3452-
t.Fatalf("top-level response status = %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String())
3453-
}
3454-
if err := json.NewDecoder(rec.Body).Decode(&sent); err != nil {
3455-
t.Fatalf("decode top-level send response: %v", err)
3456-
}
3457-
messages, err = imSvc.ListMessagesWithOptions("room-1", im.ListMessagesOptions{IncludeThreadReplies: true})
3458-
if err != nil {
3459-
t.Fatalf("ListMessagesWithOptions() after top-level response error = %v", err)
3460-
}
3461-
reply = im.Message{}
3462-
for _, message := range messages {
3463-
if message.ID == sent.MessageID {
3464-
reply = message
3465-
break
3466-
}
3467-
}
3468-
if reply.ID == "" {
3469-
t.Fatalf("top-level sent message %q not found in room messages", sent.MessageID)
3470-
}
34713421
if reply.RelatesTo != nil {
3472-
t.Fatalf("top-level reply.RelatesTo = %+v, want nil after top-level event reset", reply.RelatesTo)
3422+
t.Fatalf("reply.RelatesTo = %+v, want nil when bot send omits explicit thread/topic", reply.RelatesTo)
34733423
}
34743424
}
34753425

internal/cliproxy/auth.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ func shouldKeepExistingAuth(path string, incoming map[string]any) bool {
606606
return false
607607
}
608608
if disabled, _ := existing["disabled"].(bool); disabled {
609-
return false
609+
return true
610610
}
611611
if authMetadataEquivalent(existing, incoming) {
612612
return true

0 commit comments

Comments
 (0)