mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 06:15:25 +00:00
fix(teams): scope ListTasks by userID to prevent cross-group task leaking
Tasks from one Telegram group were being injected into another group's session because the pending-task hint and /tasks command queried all tasks team-wide without filtering by the group-scoped userID. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
9b12f0adfd
commit
361bca3617
4 files changed
+22
-11
No files matched your search
@@ -542,7 +542,7 @@ func (l *Loop) runLoop(ctx context.Context, req RunRequest) (*RunResult, error)
|
||||
// so only truly un-spawned tasks remain pending.
|
||||
if l.teamStore != nil && l.agentUUID != uuid.Nil {
|
||||
if team, _ := l.teamStore.GetTeamForAgent(ctx, l.agentUUID); team != nil && team.LeadAgentID == l.agentUUID {
|
||||
if tasks, err := l.teamStore.ListTasks(ctx, team.ID, "newest", "", ""); err == nil {
|
||||
if tasks, err := l.teamStore.ListTasks(ctx, team.ID, "newest", "", req.UserID); err == nil {
|
||||
var stale []string
|
||||
for _, t := range tasks {
|
||||
if t.Status == store.TeamTaskStatusPending {
|
||||
|
||||
@@ -190,11 +190,11 @@ func (c *Channel) handleBotCommand(ctx context.Context, message *telego.Message,
|
||||
return true
|
||||
|
||||
case "/tasks":
|
||||
c.handleTasksList(ctx, chatID, setThread)
|
||||
c.handleTasksList(ctx, chatID, isGroup, setThread)
|
||||
return true
|
||||
|
||||
case "/task_detail":
|
||||
c.handleTaskDetail(ctx, chatID, text, setThread)
|
||||
c.handleTaskDetail(ctx, chatID, text, isGroup, setThread)
|
||||
return true
|
||||
|
||||
case "/addwriter":
|
||||
|
||||
@@ -39,8 +39,17 @@ func truncateStr(s string, maxLen int) string {
|
||||
return string(runes[:maxLen]) + "…"
|
||||
}
|
||||
|
||||
// taskUserID composes the scoped user ID for task filtering.
|
||||
// Groups use "group:{channel}:{chatID}", DMs use the chat ID directly.
|
||||
func taskUserID(channelName string, chatID int64, isGroup bool) string {
|
||||
if isGroup {
|
||||
return fmt.Sprintf("group:%s:%d", channelName, chatID)
|
||||
}
|
||||
return fmt.Sprintf("%d", chatID)
|
||||
}
|
||||
|
||||
// handleTasksList handles the /tasks command — lists team tasks.
|
||||
func (c *Channel) handleTasksList(ctx context.Context, chatID int64, setThread func(*telego.SendMessageParams)) {
|
||||
func (c *Channel) handleTasksList(ctx context.Context, chatID int64, isGroup bool, setThread func(*telego.SendMessageParams)) {
|
||||
chatIDObj := tu.ID(chatID)
|
||||
|
||||
send := func(text string) {
|
||||
@@ -72,7 +81,7 @@ func (c *Channel) handleTasksList(ctx context.Context, chatID int64, setThread f
|
||||
return
|
||||
}
|
||||
|
||||
tasks, err := c.teamStore.ListTasks(ctx, team.ID, "newest", store.TeamTaskFilterAll, "")
|
||||
tasks, err := c.teamStore.ListTasks(ctx, team.ID, "newest", store.TeamTaskFilterAll, taskUserID(c.Name(), chatID, isGroup))
|
||||
if err != nil {
|
||||
slog.Warn("tasks command: ListTasks failed", "error", err)
|
||||
send("Failed to list tasks. Please try again.")
|
||||
@@ -122,7 +131,7 @@ func (c *Channel) handleTasksList(ctx context.Context, chatID int64, setThread f
|
||||
}
|
||||
|
||||
// handleTaskDetail handles the /task_detail command — shows detail for a task.
|
||||
func (c *Channel) handleTaskDetail(ctx context.Context, chatID int64, text string, setThread func(*telego.SendMessageParams)) {
|
||||
func (c *Channel) handleTaskDetail(ctx context.Context, chatID int64, text string, isGroup bool, setThread func(*telego.SendMessageParams)) {
|
||||
chatIDObj := tu.ID(chatID)
|
||||
|
||||
send := func(t string) {
|
||||
@@ -164,7 +173,7 @@ func (c *Channel) handleTaskDetail(ctx context.Context, chatID int64, text strin
|
||||
return
|
||||
}
|
||||
|
||||
tasks, err := c.teamStore.ListTasks(ctx, team.ID, "newest", store.TeamTaskFilterAll, "")
|
||||
tasks, err := c.teamStore.ListTasks(ctx, team.ID, "newest", store.TeamTaskFilterAll, taskUserID(c.Name(), chatID, isGroup))
|
||||
if err != nil {
|
||||
slog.Warn("task_detail command: ListTasks failed", "error", err)
|
||||
send("Failed to list tasks. Please try again.")
|
||||
@@ -197,9 +206,11 @@ func (c *Channel) handleCallbackQuery(ctx context.Context, query *telego.Callbac
|
||||
|
||||
taskIDStr := strings.TrimPrefix(query.Data, "td:")
|
||||
|
||||
// Resolve chat ID from the callback's message.
|
||||
chatID := query.Message.GetChat().ID
|
||||
// Resolve chat ID and group status from the callback's message.
|
||||
chat := query.Message.GetChat()
|
||||
chatID := chat.ID
|
||||
chatIDObj := tu.ID(chatID)
|
||||
isGroup := chat.Type == "group" || chat.Type == "supergroup"
|
||||
|
||||
send := func(text string) {
|
||||
for _, chunk := range chunkPlainText(text, telegramMaxMessageLen) {
|
||||
@@ -225,7 +236,7 @@ func (c *Channel) handleCallbackQuery(ctx context.Context, query *telego.Callbac
|
||||
return
|
||||
}
|
||||
|
||||
tasks, err := c.teamStore.ListTasks(ctx, team.ID, "newest", store.TeamTaskFilterAll, "")
|
||||
tasks, err := c.teamStore.ListTasks(ctx, team.ID, "newest", store.TeamTaskFilterAll, taskUserID(c.Name(), chatID, isGroup))
|
||||
if err != nil {
|
||||
send("Failed to list tasks.")
|
||||
return
|
||||
|
||||
@@ -503,7 +503,7 @@ func buildDelegateMessage(opts DelegateOpts) string {
|
||||
// pendingTasksHint returns a formatted hint listing pending team tasks,
|
||||
// so the LLM can self-correct a hallucinated team_task_id.
|
||||
func (dm *DelegateManager) pendingTasksHint(ctx context.Context, teamID uuid.UUID) string {
|
||||
tasks, err := dm.teamStore.ListTasks(ctx, teamID, "newest", "", "")
|
||||
tasks, err := dm.teamStore.ListTasks(ctx, teamID, "newest", "", store.UserIDFromContext(ctx))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user